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.

Direct Known Subclasses

DateDimension, DimensionView

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



23
24
25
# File 'lib/active_warehouse/dimension.rb', line 23

def level_orders
  @level_orders
end

.orderObject

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



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

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’


213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/active_warehouse/dimension.rb', line 213

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
  
  select_sql = "distinct #{child_level}"
  select_sql += ", #{order}" unless order == child_level
  options = {:select => select_sql, :order => order}

  options[:conditions] = conditions unless conditions.nil?
  values = []
  find(:all, options).each do |dim|
    value = dim.send(child_level_method)
    values << dim.send(child_level_method) unless values.include?(value)
  end
  values.to_a
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']


194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/active_warehouse/dimension.rb', line 194

def available_values(level)
  level_method = level.to_sym
  level = connection.quote_column_name(level.to_s)
  order = level_orders[level] || self.order || level
  
  options = {:select => "distinct #{order.to_s == level.to_s ? '' : order.to_s+','} #{level}", :order => order}
  values = []
  find(:all, options).each do |dim|
    value = dim.send(level_method)
    values << dim.send(level_method) unless values.include?(value)
  end
  values.to_a
end

.available_values_tree(hierarchy_name) ⇒ Object

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



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/active_warehouse/dimension.rb', line 253

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



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

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

.class_name(name) ⇒ Object

Convert the given name into a dimension class name



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

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.



55
56
57
58
# File 'lib/active_warehouse/dimension.rb', line 55

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}


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
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/active_warehouse/dimension.rb', line 138

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 = {}
  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



316
317
318
# File 'lib/active_warehouse/dimension.rb', line 316

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

The actual foreign key may be different and depends on the fact class. You may specify the foreign key to use for a specific fact using the Fact#set_dimension_options method.



186
187
188
# File 'lib/active_warehouse/dimension.rb', line 186

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

.hierarchiesObject

Get the ordered hierarchy names



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

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]



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

def hierarchy(name)
  hierarchy_levels[name]
end

.hierarchy_levelsObject

Get the hierarchy levels hash



73
74
75
# File 'lib/active_warehouse/dimension.rb', line 73

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



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

def last_modified
  File.new(__FILE__).mtime
end

.set_level_order(level, name) ⇒ Object

Define a column to order by for a specific level.



36
37
38
# File 'lib/active_warehouse/dimension.rb', line 36

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



31
32
33
# File 'lib/active_warehouse/dimension.rb', line 31

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



82
83
84
# File 'lib/active_warehouse/dimension.rb', line 82

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



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

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.



117
118
119
120
# File 'lib/active_warehouse/dimension.rb', line 117

def to_dimension(dimension)
  return dimension if dimension.is_a?(Class) and dimension.ancestors.include?(Dimension)
  return class_for_name(dimension)
end

Instance Method Details

#expire_value_tree_cacheObject

Expire the value tree cache



323
324
325
# File 'lib/active_warehouse/dimension.rb', line 323

def expire_value_tree_cache
  self.class.expire_value_tree_cache
end