Class: ActiveWarehouse::Dimension

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
HierarchicalDimension, SlowlyChangingDimension
Defined in:
lib/active_warehouse/dimension.rb

Overview

Dimension tables contain the textual descriptors of the business. Dimensions provide the filters which are applied to facts. Dimensions are the primary source of query constraints, groupings and report labels.

Defined Under Namespace

Classes: Node

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from SlowlyChangingDimension

included

Methods included from HierarchicalDimension

included

Class Attribute Details

.level_ordersObject (readonly)

Get the level orders map



21
22
23
# File 'lib/active_warehouse/dimension.rb', line 21

def level_orders
  @level_orders
end

.orderObject

Alternate order by, to be used rather than the current level being queried



18
19
20
# File 'lib/active_warehouse/dimension.rb', line 18

def order
  @order
end

Class Method Details

.available_child_values(hierarchy_name, parent_values) ⇒ Object Also known as: available_children_values

Get an array of child values for a particular parent in the hierachy For example, given a DateDimension with data from 2002 to 2004:

available_child_values(:cy, [2002, ‘Q1’]) returns [‘January’, ‘Feburary’, ‘March’, ‘April’]



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/active_warehouse/dimension.rb', line 188

def available_child_values(hierarchy_name, parent_values)
  if hierarchy_levels[hierarchy_name].nil?
    raise ArgumentError, "The hierarchy '#{hierarchy_name}' does not exist in your dimension #{name}"
  end

  levels = hierarchy_levels[hierarchy_name]
  if levels.length <= parent_values.length
    raise ArgumentError, "The parent_values '#{parent_values.to_yaml}' exceeds the hierarchy depth #{levels.to_yaml}"
  end
  
  child_level = levels[parent_values.length].to_s
  
  # Create the conditions array. Will work with 1.1.6.
  conditions_parts = []
  conditions_values = []
  parent_values.each_with_index do |value, index|
    conditions_parts << "#{levels[index]} = ?"
    conditions_values << value
  end
  conditions = [conditions_parts.join(' AND ')] + conditions_values unless conditions_parts.empty?
  
  child_level_method = child_level.to_sym
  child_level = connection.quote_column_name(child_level)
  order = level_orders[child_level] || self.order || child_level
  
  options = {:select => child_level, :group => child_level, :order => order}
  options[:conditions] = conditions unless conditions.nil?
  find(:all, options).collect {|dim| dim.send(child_level_method)}
end

.available_values(level) ⇒ Object

Get an array of the available values for a particular hierarchy level For example, given a DateDimension with data from 2002 to 2004:

available_values('calendar_year') returns ['2002','2003','2004']


177
178
179
180
181
182
# File 'lib/active_warehouse/dimension.rb', line 177

def available_values(level)
  level_method = level.to_sym
  level = connection.quote_column_name(level.to_s)
  order = level_orders[level] || self.order || level
  find(:all, :select => level, :group => level, :order => order).collect {|dim| dim.send(level_method)}
end

.available_values_tree(hierarchy_name) ⇒ Object

Get a tree of Node objects for all of the values in the specified hierarchy.



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/active_warehouse/dimension.rb', line 220

def available_values_tree(hierarchy_name)
  root = value_tree_cache[hierarchy_name]
  if root.nil?
    root = Node.new('All', '__ROOT__')
    levels = hierarchy(hierarchy_name)
    nodes = {nil => root}
    level_list = levels.collect{|level| connection.quote_column_name(level) }.join(',')
    order = self.order || level_list
    find(:all, :select => level_list, :group => level_list, :order => order).each do |dim|
      parent_node = root
      levels.each do |level|
        node_value = dim.send(level)
        child_node = parent_node.optionally_add_child(node_value, level)
        parent_node = child_node
      end
    end
    value_tree_cache[hierarchy_name] = root
  end
  root
end

.class_for_name(name) ⇒ Object

Get a class for the specified named dimension



98
99
100
# File 'lib/active_warehouse/dimension.rb', line 98

def class_for_name(name)
  class_name(name).constantize
end

.class_name(name) ⇒ Object

Convert the given name into a dimension class name



91
92
93
94
95
# File 'lib/active_warehouse/dimension.rb', line 91

def class_name(name)
  dimension_name = name.to_s
  dimension_name = "#{dimension_name}_dimension" unless dimension_name =~ /_dimension$/
  dimension_name.classify
end

.define_hierarchy(name, levels) ⇒ Object

Define a named attribute hierarchy in the dimension.

Example: define_hierarchy(:fiscal_calendar, [:fiscal_year, :fiscal_quarter, :fiscal_month])

This would indicate that one of the drill down paths for this dimension is: Fiscal Year -> Fiscal Quarter -> Fiscal Month

Internally the hierarchies are stored in order. The first hierarchy defined will be used as the default if no hierarchy is specified when rendering a cube.



51
52
53
54
# File 'lib/active_warehouse/dimension.rb', line 51

def define_hierarchy(name, levels)
  hierarchies << name
  hierarchy_levels[name] = levels
end

.denominator_count(hierarchy_name, level, denominator_level = nil) ⇒ Object

Returns a hash of all of the values at the specified hierarchy level mapped to the count at that level. For example, given a date dimension with years from 2002 to 2004 and a hierarchy defined with:

hierarchy :cy, [:calendar_year, :calendar_quarter, :calendar_month_name]

…then…

DateDimension.denominator_count(:cy, :calendar_year, :calendar_quarter) returns {'2002' => 4, '2003' => 4, '2004' => 4}

If the denominator_level parameter is omitted or nil then:

DateDimension.denominator_count(:cy, :calendar_year) returns {'2003' => 365, '2003' => 365, '2004' => 366}


128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/active_warehouse/dimension.rb', line 128

def denominator_count(hierarchy_name, level, denominator_level=nil)
  if hierarchy_levels[hierarchy_name].nil?
    raise ArgumentError, "The hierarchy '#{hierarchy_name}' does not exist in your dimension #{name}"
  end
  
  q = nil
  # If the denominator_level is specified and it is not the last element in the hierarchy then do a distinct count. If
  # the denominator level is less than the current level then raise an ArgumentError. In other words, if the current level is
  # calendar month then passing in calendar year as the denominator level would raise an ArgumentErro.
  #
  # If the denominator_level is not specified then assume the finest grain possible (in the context of a date dimension
  # this would be each day) and use the id to count.
  if denominator_level && hierarchy_levels[hierarchy_name].last != denominator_level
    level_index = hierarchy_levels[hierarchy_name].index(level)
    denominator_index = hierarchy_levels[hierarchy_name].index(denominator_level)

    if level_index.nil?
      raise ArgumentError, "The level '#{level}' does not appear to exist"
    end
    if denominator_index.nil?
      raise ArgumentError, "The denominator level '#{denominator_level}' does not appear to exist"
    end
    if hierarchy_levels[hierarchy_name].index(denominator_level) < hierarchy_levels[hierarchy_name].index(level)
      raise ArgumentError, "The index of the denominator level '#{denominator_level}' in the hierarchy '#{hierarchy_name}' must be greater than or equal to the level '#{level}'"
    end

    q = "select #{level} as level, count(distinct(#{denominator_level})) as level_count from #{table_name} group by #{level}"
  else
    q = "select #{level} as level, count(id) as level_count from #{table_name} group by #{level}"
  end
  denominators = {}
  # TODO: fix to use select_all instead of execute
  connection.select_all(q).each do |row|
    denominators[row['level']] = row['level_count'].to_i
  end
  denominators
end

.expire_value_tree_cacheObject

Expire the value tree cache. This should be called if the dimension



283
284
285
# File 'lib/active_warehouse/dimension.rb', line 283

def expire_value_tree_cache
  @value_tree_cache = nil
end

.foreign_keyObject

Get the foreign key for this dimension which is used in Fact tables.

Example: DateDimension would have a foreign key of date_id



169
170
171
# File 'lib/active_warehouse/dimension.rb', line 169

def foreign_key
  table_name.sub(/_dimension/,'') + '_id'
end

.hierarchiesObject

Get the ordered hierarchy names



64
65
66
# File 'lib/active_warehouse/dimension.rb', line 64

def hierarchies
  @hierarchies ||= []
end

.hierarchy(name) ⇒ Object

Get the named attribute hierarchy. Returns an array of column names.

Example: hierarchy(:fiscal_calendar) might return [:fiscal_year, :fiscal_quarter, :fiscal_month]



59
60
61
# File 'lib/active_warehouse/dimension.rb', line 59

def hierarchy(name)
  hierarchy_levels[name]
end

.hierarchy_levelsObject

Get the hierarchy levels hash



69
70
71
# File 'lib/active_warehouse/dimension.rb', line 69

def hierarchy_levels
  @hierarchy_levels ||= {}
end

.last_modifiedObject

Return the time when the underlying dimension source file was last modified. This is used to determine if a cube structure rebuild is required



104
105
106
# File 'lib/active_warehouse/dimension.rb', line 104

def last_modified
  File.new(__FILE__).mtime
end

.set_level_order(level, name) ⇒ Object

Define a column to order by for a specific level.



33
34
35
# File 'lib/active_warehouse/dimension.rb', line 33

def set_level_order(level, name)
  level_orders[level] = name
end

.set_order(name) ⇒ Object

Define a column to order by. If this value is specified then it will be used rather than the actual level being queried in the following method calls:

  • available_values

  • available_child_values

  • available_values_tree



28
29
30
# File 'lib/active_warehouse/dimension.rb', line 28

def set_order(name)
  @order = name
end

.symObject

Return a symbol used when referring to this dimension. The symbol is calculated by demodulizing and underscoring the dimension’s class name and then removing the trailing _dimension.

Example: DateDimension will return a symbol :date



77
78
79
# File 'lib/active_warehouse/dimension.rb', line 77

def sym
  self.name.demodulize.underscore.gsub(/_dimension/, '').to_sym
end

.table_nameObject

Get the table name. By default the table name will be the name of the dimension in singular form.

Example: DateDimension will have a table called date_dimension



84
85
86
87
88
# File 'lib/active_warehouse/dimension.rb', line 84

def table_name
  name = self.name.demodulize.underscore
  set_table_name(name)
  name
end

.to_dimension(dimension) ⇒ Object

Get the dimension class for the specified dimension parameter. The dimension parameter may be a class, String or Symbol.



110
111
112
113
# File 'lib/active_warehouse/dimension.rb', line 110

def to_dimension(dimension)
  return dimension if dimension.is_a?(Class) and dimension.superclass == Dimension
  return class_for_name(dimension)
end

Instance Method Details

#expire_value_tree_cacheObject



289
290
291
# File 'lib/active_warehouse/dimension.rb', line 289

def expire_value_tree_cache
  self.class.expire_value_tree_cache
end