activewarehouse 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. data/README +27 -14
  2. data/Rakefile +16 -5
  3. data/doc/references.txt +4 -0
  4. data/generators/bridge/templates/migration.rb +9 -2
  5. data/generators/bridge/templates/unit_test.rb +8 -0
  6. data/generators/date_dimension/USAGE +1 -0
  7. data/generators/date_dimension/date_dimension_generator.rb +16 -0
  8. data/generators/date_dimension/templates/fixture.yml +5 -0
  9. data/generators/date_dimension/templates/migration.rb +31 -0
  10. data/generators/date_dimension/templates/model.rb +3 -0
  11. data/generators/date_dimension/templates/unit_test.rb +8 -0
  12. data/generators/dimension/templates/migration.rb +1 -10
  13. data/generators/dimension_view/dimension_view_generator.rb +2 -2
  14. data/generators/dimension_view/templates/migration.rb +8 -2
  15. data/generators/fact/templates/migration.rb +2 -0
  16. data/generators/time_dimension/USAGE +1 -0
  17. data/generators/time_dimension/templates/fixture.yml +5 -0
  18. data/generators/time_dimension/templates/migration.rb +12 -0
  19. data/generators/time_dimension/templates/model.rb +3 -0
  20. data/generators/time_dimension/templates/unit_test.rb +8 -0
  21. data/generators/time_dimension/time_dimension_generator.rb +14 -0
  22. data/lib/active_warehouse.rb +13 -2
  23. data/lib/active_warehouse/aggregate.rb +54 -253
  24. data/lib/active_warehouse/aggregate/dwarf/node.rb +36 -0
  25. data/lib/active_warehouse/aggregate/dwarf_aggregate.rb +369 -0
  26. data/lib/active_warehouse/aggregate/dwarf_common.rb +44 -0
  27. data/lib/active_warehouse/aggregate/dwarf_printer.rb +34 -0
  28. data/lib/active_warehouse/aggregate/no_aggregate.rb +194 -0
  29. data/lib/active_warehouse/aggregate/pid_aggregate.rb +29 -0
  30. data/lib/active_warehouse/aggregate/pipelined_rolap_aggregate.rb +129 -0
  31. data/lib/active_warehouse/aggregate/rolap_aggregate.rb +181 -0
  32. data/lib/active_warehouse/aggregate/rolap_common.rb +89 -0
  33. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_1.sql +12 -0
  34. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_10.sql +7166 -0
  35. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_11.sql +14334 -0
  36. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_12.sql +28670 -0
  37. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_13.sql +57342 -0
  38. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_2.sql +26 -0
  39. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_3.sql +54 -0
  40. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_4.sql +110 -0
  41. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_5.sql +222 -0
  42. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_6.sql +446 -0
  43. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_7.sql +894 -0
  44. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_8.sql +1790 -0
  45. data/lib/active_warehouse/aggregate/templates/pipelined_rollup_9.sql +3582 -0
  46. data/lib/active_warehouse/aggregate_field.rb +49 -0
  47. data/lib/active_warehouse/{dimension/bridge.rb → bridge.rb} +7 -3
  48. data/lib/active_warehouse/bridge/hierarchy_bridge.rb +46 -0
  49. data/lib/active_warehouse/builder.rb +2 -1
  50. data/lib/active_warehouse/builder/date_dimension_builder.rb +5 -2
  51. data/lib/active_warehouse/builder/generator/generator.rb +13 -0
  52. data/lib/active_warehouse/builder/generator/name_generator.rb +20 -0
  53. data/lib/active_warehouse/builder/generator/paragraph_generator.rb +11 -0
  54. data/lib/active_warehouse/builder/random_data_builder.rb +21 -11
  55. data/lib/active_warehouse/builder/test_data_builder.rb +54 -0
  56. data/lib/active_warehouse/calculated_field.rb +27 -0
  57. data/lib/active_warehouse/compat/compat.rb +4 -4
  58. data/lib/active_warehouse/cube.rb +126 -225
  59. data/lib/active_warehouse/cube_query_result.rb +69 -0
  60. data/lib/active_warehouse/dimension.rb +64 -29
  61. data/lib/active_warehouse/dimension/date_dimension.rb +15 -0
  62. data/lib/active_warehouse/dimension/dimension_reflection.rb +21 -0
  63. data/lib/active_warehouse/dimension/dimension_view.rb +17 -2
  64. data/lib/active_warehouse/dimension/hierarchical_dimension.rb +43 -5
  65. data/lib/active_warehouse/dimension/slowly_changing_dimension.rb +22 -12
  66. data/lib/active_warehouse/fact.rb +119 -40
  67. data/lib/active_warehouse/field.rb +74 -0
  68. data/lib/active_warehouse/ordered_hash.rb +34 -0
  69. data/lib/active_warehouse/prejoin_fact.rb +97 -0
  70. data/lib/active_warehouse/report/abstract_report.rb +40 -14
  71. data/lib/active_warehouse/report/chart_report.rb +3 -3
  72. data/lib/active_warehouse/report/table_report.rb +8 -3
  73. data/lib/active_warehouse/version.rb +1 -1
  74. data/lib/active_warehouse/view/report_helper.rb +144 -34
  75. data/tasks/active_warehouse_tasks.rake +28 -10
  76. metadata +107 -30
@@ -0,0 +1,69 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ # Class that holds the results of a Cube query
3
+ class CubeQueryResult
4
+ attr_reader :aggregate_fields_hash
5
+
6
+ # Initialize the aggregate map with an array of AggregateField instances.
7
+ # The AggregateFields are used to typecast the raw values coming from
8
+ # the database. Thank you very little, DBI.
9
+ def initialize(aggregate_fields)
10
+ raise ArgumentError, "aggregate_fields must not be empty" unless aggregate_fields && aggregate_fields.size > 0
11
+ @aggregate_fields_hash = {}
12
+ aggregate_fields.each {|c| @aggregate_fields_hash[c.label] = c}
13
+ @values_map = {}
14
+ end
15
+
16
+ # Return true if the aggregate map includes the specified row value
17
+ def has_row_values?(row_value)
18
+ @values_map.has_key?(row_value)
19
+ end
20
+
21
+ # iterate through every row and column combination
22
+ def each
23
+ @values_map.each do |key, value|
24
+ yield key, value
25
+ end
26
+ end
27
+
28
+ def value(row_value, col_value, field_label)
29
+ #puts "getting value #{row_value},#{col_value},#{field_label}"
30
+ values(row_value, col_value)[field_label]
31
+ end
32
+
33
+ # returns a hash of type casted fact values for the intersection of
34
+ # row_value and col_value
35
+ def values(row_value, col_value)
36
+ row = @values_map[row_value]
37
+ return empty_hash_for_missing_row_or_column if row.nil?
38
+ facts = row[col_value]
39
+ return empty_hash_for_missing_row_or_column if facts.nil?
40
+ facts
41
+ end
42
+
43
+ # Add a hash of aggregated facts for the given row and column values.
44
+ # For instance, add_data('Southeast', 2005, {:sales_sum => 40000, :sales_count => 40})
45
+ # This method will typecast the values in aggregated_facts.
46
+ def add_data(row_value, col_value, aggregated_facts)
47
+ #puts "Adding data for #{row_value}, #{col_value} [data=[#{aggregated_facts.join(',')}]]"
48
+ @values_map[row_value] ||= {}
49
+ @values_map[row_value][col_value] = typecast_facts(aggregated_facts)
50
+ end
51
+
52
+ private
53
+ def empty_hash_for_missing_row_or_column
54
+ empty = {}
55
+ aggregate_fields_hash.keys.each {|k| empty[k] = 0}
56
+ empty
57
+ end
58
+
59
+ def typecast_facts(raw_facts)
60
+ raw_facts.each do |k,v|
61
+ field = aggregate_fields_hash[k]
62
+ if field.nil?
63
+ raise ArgumentError, "'#{k}' is an unknown aggregate field in this query result"
64
+ end
65
+ raw_facts[k] = field.type_cast(v)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,12 +1,14 @@
1
1
  # require all of the "acts_as" mixins first
2
2
  require 'active_warehouse/dimension/hierarchical_dimension'
3
3
  require 'active_warehouse/dimension/slowly_changing_dimension'
4
+ require 'active_warehouse/dimension/dimension_reflection'
5
+
6
+ ActiveRecord::Reflection::AssociationReflection.send(:include, ActiveWarehouse::DimensionReflection)
4
7
 
5
8
  module ActiveWarehouse #:nodoc
6
- # Dimension tables contain the textual descriptors of the business. Dimensions provide the filters which
7
- # are applied to facts. Dimensions are the primary source of query constraints, groupings and report
8
- # labels.
9
- #
9
+ # Dimension tables contain the textual descriptors of the business. Dimensions
10
+ # provide the filters which are applied to facts. Dimensions are the primary
11
+ # source of query constraints, groupings and report labels.
10
12
  class Dimension < ActiveRecord::Base
11
13
  include ActiveWarehouse::HierarchicalDimension
12
14
  include ActiveWarehouse::SlowlyChangingDimension
@@ -20,8 +22,9 @@ module ActiveWarehouse #:nodoc
20
22
  # Map of level names to alternate order columns
21
23
  attr_reader :level_orders
22
24
 
23
- # Define a column to order by. If this value is specified then it will be used rather than the actual
24
- # level being queried in the following method calls:
25
+ # Define a column to order by. If this value is specified then it will be
26
+ # used rather than the actual level being queried in the following method
27
+ # calls:
25
28
  # * available_values
26
29
  # * available_child_values
27
30
  # * available_values_tree
@@ -46,8 +49,9 @@ module ActiveWarehouse #:nodoc
46
49
  # This would indicate that one of the drill down paths for this dimension is:
47
50
  # Fiscal Year -> Fiscal Quarter -> Fiscal Month
48
51
  #
49
- # Internally the hierarchies are stored in order. The first hierarchy defined will be used as the default
50
- # if no hierarchy is specified when rendering a cube.
52
+ # Internally the hierarchies are stored in order. The first hierarchy
53
+ # defined will be used as the default if no hierarchy is specified when
54
+ # rendering a cube.
51
55
  def define_hierarchy(name, levels)
52
56
  hierarchies << name
53
57
  hierarchy_levels[name] = levels
@@ -70,7 +74,8 @@ module ActiveWarehouse #:nodoc
70
74
  @hierarchy_levels ||= {}
71
75
  end
72
76
 
73
- # Return a symbol used when referring to this dimension. The symbol is calculated by demodulizing and underscoring the
77
+ # Return a symbol used when referring to this dimension. The symbol is
78
+ # calculated by demodulizing and underscoring the
74
79
  # dimension's class name and then removing the trailing _dimension.
75
80
  #
76
81
  # Example: DateDimension will return a symbol :date
@@ -78,7 +83,8 @@ module ActiveWarehouse #:nodoc
78
83
  self.name.demodulize.underscore.gsub(/_dimension/, '').to_sym
79
84
  end
80
85
 
81
- # Get the table name. By default the table name will be the name of the dimension in singular form.
86
+ # Get the table name. By default the table name will be the name of the
87
+ # dimension in singular form.
82
88
  #
83
89
  # Example: DateDimension will have a table called date_dimension
84
90
  def table_name
@@ -99,31 +105,35 @@ module ActiveWarehouse #:nodoc
99
105
  class_name(name).constantize
100
106
  end
101
107
 
102
- # Return the time when the underlying dimension source file was last modified. This is used
108
+ # Return the time when the underlying dimension source file was last
109
+ # modified. This is used
103
110
  # to determine if a cube structure rebuild is required
104
111
  def last_modified
105
112
  File.new(__FILE__).mtime
106
113
  end
107
114
 
108
- # Get the dimension class for the specified dimension parameter. The dimension parameter may be a class,
109
- # String or Symbol.
115
+ # Get the dimension class for the specified dimension parameter. The
116
+ # dimension parameter may be a class, String or Symbol.
110
117
  def to_dimension(dimension)
111
- return dimension if dimension.is_a?(Class) and dimension.superclass == Dimension
118
+ return dimension if dimension.is_a?(Class) and dimension.ancestors.include?(Dimension)
112
119
  return class_for_name(dimension)
113
120
  end
114
121
 
115
- # Returns a hash of all of the values at the specified hierarchy level mapped to the count at that level.
116
- # For example, given a date dimension with years from 2002 to 2004 and a hierarchy defined with:
122
+ # Returns a hash of all of the values at the specified hierarchy level
123
+ # mapped to the count at that level. For example, given a date dimension
124
+ # with years from 2002 to 2004 and a hierarchy defined with:
117
125
  #
118
126
  # hierarchy :cy, [:calendar_year, :calendar_quarter, :calendar_month_name]
119
127
  #
120
128
  # ...then...
121
129
  #
122
- # DateDimension.denominator_count(:cy, :calendar_year, :calendar_quarter) returns {'2002' => 4, '2003' => 4, '2004' => 4}
130
+ # DateDimension.denominator_count(:cy, :calendar_year, :calendar_quarter)
131
+ # returns {'2002' => 4, '2003' => 4, '2004' => 4}
123
132
  #
124
133
  # If the denominator_level parameter is omitted or nil then:
125
134
  #
126
- # DateDimension.denominator_count(:cy, :calendar_year) returns {'2003' => 365, '2003' => 365, '2004' => 366}
135
+ # DateDimension.denominator_count(:cy, :calendar_year) returns
136
+ # {'2003' => 365, '2003' => 365, '2004' => 366}
127
137
  #
128
138
  def denominator_count(hierarchy_name, level, denominator_level=nil)
129
139
  if hierarchy_levels[hierarchy_name].nil?
@@ -131,12 +141,16 @@ module ActiveWarehouse #:nodoc
131
141
  end
132
142
 
133
143
  q = nil
134
- # If the denominator_level is specified and it is not the last element in the hierarchy then do a distinct count. If
135
- # the denominator level is less than the current level then raise an ArgumentError. In other words, if the current level is
136
- # calendar month then passing in calendar year as the denominator level would raise an ArgumentErro.
144
+ # If the denominator_level is specified and it is not the last element
145
+ # in the hierarchy then do a distinct count. If
146
+ # the denominator level is less than the current level then raise an
147
+ # ArgumentError. In other words, if the current level is
148
+ # calendar month then passing in calendar year as the denominator level
149
+ # would raise an ArgumentErro.
137
150
  #
138
- # If the denominator_level is not specified then assume the finest grain possible (in the context of a date dimension
139
- # this would be each day) and use the id to count.
151
+ # If the denominator_level is not specified then assume the finest grain
152
+ # possible (in the context of a date dimension this would be each day)
153
+ # and use the id to count.
140
154
  if denominator_level && hierarchy_levels[hierarchy_name].last != denominator_level
141
155
  level_index = hierarchy_levels[hierarchy_name].index(level)
142
156
  denominator_index = hierarchy_levels[hierarchy_name].index(denominator_level)
@@ -156,7 +170,6 @@ module ActiveWarehouse #:nodoc
156
170
  q = "select #{level} as level, count(id) as level_count from #{table_name} group by #{level}"
157
171
  end
158
172
  denominators = {}
159
- # TODO: fix to use select_all instead of execute
160
173
  connection.select_all(q).each do |row|
161
174
  denominators[row['level']] = row['level_count'].to_i
162
175
  end
@@ -166,6 +179,10 @@ module ActiveWarehouse #:nodoc
166
179
  # Get the foreign key for this dimension which is used in Fact tables.
167
180
  #
168
181
  # Example: DateDimension would have a foreign key of date_id
182
+ #
183
+ # The actual foreign key may be different and depends on the fact class.
184
+ # You may specify the foreign key to use for a specific fact using the
185
+ # Fact#set_dimension_options method.
169
186
  def foreign_key
170
187
  table_name.sub(/_dimension/,'') + '_id'
171
188
  end
@@ -178,13 +195,21 @@ module ActiveWarehouse #:nodoc
178
195
  level_method = level.to_sym
179
196
  level = connection.quote_column_name(level.to_s)
180
197
  order = level_orders[level] || self.order || level
181
- find(:all, :select => level, :group => level, :order => order).collect {|dim| dim.send(level_method)}
198
+
199
+ options = {:select => "distinct #{order.to_s == level.to_s ? '' : order.to_s+','} #{level}", :order => order}
200
+ values = []
201
+ find(:all, options).each do |dim|
202
+ value = dim.send(level_method)
203
+ values << dim.send(level_method) unless values.include?(value)
204
+ end
205
+ values.to_a
182
206
  end
183
207
 
184
208
  # Get an array of child values for a particular parent in the hierachy
185
209
  # For example, given a DateDimension with data from 2002 to 2004:
186
210
  #
187
- # available_child_values(:cy, [2002, 'Q1']) returns ['January', 'Feburary', 'March', 'April']
211
+ # available_child_values(:cy, [2002, 'Q1']) returns
212
+ # ['January', 'Feburary', 'March', 'April']
188
213
  def available_child_values(hierarchy_name, parent_values)
189
214
  if hierarchy_levels[hierarchy_name].nil?
190
215
  raise ArgumentError, "The hierarchy '#{hierarchy_name}' does not exist in your dimension #{name}"
@@ -210,9 +235,17 @@ module ActiveWarehouse #:nodoc
210
235
  child_level = connection.quote_column_name(child_level)
211
236
  order = level_orders[child_level] || self.order || child_level
212
237
 
213
- options = {:select => child_level, :group => child_level, :order => order}
238
+ select_sql = "distinct #{child_level}"
239
+ select_sql += ", #{order}" unless order == child_level
240
+ options = {:select => select_sql, :order => order}
241
+
214
242
  options[:conditions] = conditions unless conditions.nil?
215
- find(:all, options).collect {|dim| dim.send(child_level_method)}
243
+ values = []
244
+ find(:all, options).each do |dim|
245
+ value = dim.send(child_level_method)
246
+ values << dim.send(child_level_method) unless values.include?(value)
247
+ end
248
+ values.to_a
216
249
  end
217
250
  alias :available_children_values :available_child_values
218
251
 
@@ -286,6 +319,7 @@ module ActiveWarehouse #:nodoc
286
319
  end
287
320
 
288
321
  public
322
+ # Expire the value tree cache
289
323
  def expire_value_tree_cache
290
324
  self.class.expire_value_tree_cache
291
325
  end
@@ -293,4 +327,5 @@ module ActiveWarehouse #:nodoc
293
327
  end
294
328
  end
295
329
 
296
- require 'active_warehouse/dimension/bridge'
330
+ require 'active_warehouse/dimension/date_dimension'
331
+ require 'active_warehouse/dimension/dimension_view'
@@ -0,0 +1,15 @@
1
+ module ActiveWarehouse
2
+ # Explicit date dimension, because Date dimension has special columns
3
+ # which can be configured to have different names.
4
+ class DateDimension < Dimension
5
+ class << self
6
+ def set_sql_date_stamp(name)
7
+ @sql_date_stamp = name
8
+ end
9
+
10
+ def sql_date_stamp
11
+ @sql_date_stamp ||= "sql_date_stamp"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveWarehouse
2
+ module DimensionReflection
3
+ attr_reader :slowly_changing_over
4
+
5
+ def slowly_changing_over=(reflection)
6
+ @slowly_changing_over = reflection
7
+ add_dependent_dimension_reflection(reflection)
8
+ end
9
+
10
+ # add a dependent dimension reflection to this dimension reflection.
11
+ # some dimensions require others to operate, e.g. slowly changing dimensions
12
+ def add_dependent_dimension_reflection(dimension_reflection)
13
+ dependent_dimension_reflections << dimension_reflection
14
+ end
15
+
16
+ # returns array of dependent dimension reflections
17
+ def dependent_dimension_reflections
18
+ @dependent_dimension_reflections ||= []
19
+ end
20
+ end
21
+ end
@@ -3,9 +3,24 @@ module ActiveWarehouse #:nodoc
3
3
  # a view for an existing dimension. A common use is to provide a date dimension and then provide numerous
4
4
  # role-playing dimensions implemented as views to the date dimension, such as Order Date Dimension,
5
5
  # Shipping Date Dimension, etc.
6
- class DimensionView < ActiveRecord::Base
6
+ class DimensionView < Dimension
7
7
  class << self
8
-
8
+ def set_order(name)
9
+ super("#{self.sym}_#{name}".to_sym)
10
+ end
11
+ def define_hierarchy(name, hierarchy)
12
+ super(name, hierarchy.collect { |name| "#{self.sym}_#{name}".to_sym })
13
+ end
14
+ end
15
+ def method_missing(method_name, *args)
16
+ unless method_name.to_s =~ /^#{self.class.sym}_/
17
+ method_name = "#{self.class.sym}_#{method_name}".to_sym
18
+ end
19
+ if attribute_present?(method_name)
20
+ read_attribute(method_name)
21
+ else
22
+ raise NameError, "Attribute #{method_name} not found in #{self.class}"
23
+ end
9
24
  end
10
25
  end
11
26
  end
@@ -24,8 +24,45 @@ module ActiveWarehouse #:nodoc
24
24
 
25
25
  # Get the bridge class name for this hierarchical dimension
26
26
  def bridge_class_name
27
- name.gsub(/Dimension$/, '') + 'HierarchyBridge'
27
+ @child_hierarchy_relationship.class_name || @parent_hierarchy_relationship.class_name
28
28
  end
29
+
30
+ # Define the child relationship on the bridge table to the dimension
31
+ # table. We can specify a different class name for the bridge table
32
+ # and different foreign-key.
33
+ def child_bridge(association_id, options = {})
34
+ options[:class_name] ||= name.gsub(/Dimension$/, 'HierarchyBridge')
35
+ options[:foreign_key] ||= "parent_id"
36
+ has_many association_id, options
37
+ @child_hierarchy_relationship = reflections[association_id]
38
+ end
39
+
40
+ # Define the parent relationship on the bridge table to the dimension
41
+ # table.
42
+ def parent_bridge(association_id, options = {})
43
+ options[:class_name] ||= name.gsub(/Dimension$/, 'HierarchyBridge')
44
+ options[:foreign_key] ||= "child_id"
45
+ has_many association_id, options
46
+ @parent_hierarchy_relationship = reflections[association_id]
47
+ end
48
+
49
+ # the foreign key column name on the bridge table for finding the
50
+ # children.
51
+ def child_foreign_key
52
+ @child_hierarchy_relationship.primary_key_name
53
+ end
54
+
55
+ # the foreign key column name on the bridge table for finding the
56
+ # parent.
57
+ def parent_foreign_key
58
+ @parent_hierarchy_relationship.primary_key_name
59
+ end
60
+
61
+ # the column name on the bridge table that defines the number of levels
62
+ # from the parent
63
+ def levels_from_parent
64
+ bridge_class.levels_from_parent
65
+ end
29
66
  end
30
67
  end
31
68
  include InstanceMethods
@@ -36,6 +73,7 @@ module ActiveWarehouse #:nodoc
36
73
  def hierarchical_dimension?
37
74
  self.included_modules.include?(InstanceMethods)
38
75
  end
76
+
39
77
  end
40
78
 
41
79
  module InstanceMethods #:nodoc
@@ -43,16 +81,16 @@ module ActiveWarehouse #:nodoc
43
81
  def parent
44
82
  self.class.find(:first,
45
83
  :select => "a.*",
46
- :joins => "a join #{self.class.bridge_class.table_name} b on a.id = b.parent_id",
47
- :conditions => ['b.child_id = ? and b.levels_from_parent = 1', self.id])
84
+ :joins => "a join #{self.class.bridge_class.table_name} b on a.id = b.#{self.class.child_foreign_key}",
85
+ :conditions => ["b.#{self.class.parent_foreign_key} = ? and b.#{self.class.levels_from_parent} = 1", self.id])
48
86
  end
49
87
 
50
88
  # Get the children for this node
51
89
  def children
52
90
  self.class.find(:all,
53
91
  :select => "a.*",
54
- :joins => "a join #{self.class.bridge_class.table_name} b on a.id = b.child_id",
55
- :conditions => ['b.parent_id = ? and b.levels_from_parent = 1', self.id])
92
+ :joins => "a join #{self.class.bridge_class.table_name} b on a.id = b.#{self.class.parent_foreign_key}",
93
+ :conditions => ["b.#{self.class.child_foreign_key} = ? and b.#{self.class.levels_from_parent} = 1", self.id])
56
94
  end
57
95
  end
58
96
 
@@ -29,10 +29,10 @@ module ActiveWarehouse #:nodoc:
29
29
  # This is necessary because the find query used when :valid_on is specified is implemented using a between clause.
30
30
  #
31
31
  # Options:
32
- # *<tt>:identifier</tt>:
33
- # *<tt>:lastest_version</tt>: Define the attribute name which represents the latest version flag
34
- # *<tt>:effective_date</tt>: Define the attribute name which represents the effective date column
35
- # *<tt>:expiration_date</tt>: Define the attribute name which represents the expiration date column
32
+ # * <tt>:identifier</tt>:
33
+ # * <tt>:lastest_version</tt>: Define the attribute name which represents the latest version flag
34
+ # * <tt>:effective_date</tt>: Define the attribute name which represents the effective date column
35
+ # * <tt>:expiration_date</tt>: Define the attribute name which represents the expiration date column
36
36
  #
37
37
  def acts_as_slowly_changing_dimension(options = {})
38
38
  unless slowly_changing_dimension? # don't let AR call this twice
@@ -50,7 +50,7 @@ module ActiveWarehouse #:nodoc:
50
50
  alias_method :core_validate_find_options, :validate_find_options
51
51
  VALID_FIND_OPTIONS << :with_older
52
52
  VALID_FIND_OPTIONS << :valid_on
53
- VALID_FIND_OPTIONS << :valid_on_or_after
53
+ VALID_FIND_OPTIONS << :valid_during
54
54
  end
55
55
  end
56
56
  include InstanceMethods
@@ -103,7 +103,7 @@ module ActiveWarehouse #:nodoc:
103
103
  protected
104
104
  def with_older_scope(&block)
105
105
  with_scope({:find => { :conditions =>
106
- "#{table_name}.#{latest_version_attribute} = 1" } }, :merge, &block)
106
+ ["#{table_name}.#{latest_version_attribute} = ?", true] } }, :merge, &block)
107
107
  end
108
108
 
109
109
  def with_valid_on_scope(valid_on, &block)
@@ -112,19 +112,29 @@ module ActiveWarehouse #:nodoc:
112
112
  "and #{expiration_date_attribute}", valid_on]} }, :merge, &block)
113
113
  end
114
114
 
115
- def with_valid_on_or_after_scope(valid_on_or_after, &block)
115
+ def with_valid_during_scope(valid_during, &block)
116
116
  with_scope({:find => {:conditions =>
117
- ["#{effective_date_attribute} <= ? and #{expiration_date_attribute} > ?",
118
- valid_on_or_after, valid_on_or_after]} }, :merge, &block)
117
+ ["(? between #{effective_date_attribute} and #{expiration_date_attribute})" +
118
+ " or (#{effective_date_attribute} between ? and ?)",
119
+ valid_during.first, valid_during.first, valid_during.last]} }, :merge, &block)
119
120
  end
120
-
121
+
121
122
  private
122
123
  # all find calls lead here
123
124
  def find_every(options)
124
125
  if options.include?(:valid_on)
125
126
  with_valid_on_scope(options[:valid_on]) { find_every_with_older(options) }
126
- elsif options.include?(:valid_on_or_after)
127
- with_valid_on_or_after_scope(options[:valid_on_or_after]) { find_every_with_older(options) }
127
+ elsif options.include?(:valid_during)
128
+ if !options.include?(:order)
129
+ options[:order] = "#{effective_date_attribute} asc"
130
+ end
131
+ if !options.include?(:limit)
132
+ options[:limit] = 1
133
+ end
134
+ if !options.include?(:offset)
135
+ options[:offset] = 0
136
+ end
137
+ with_valid_during_scope(options[:valid_during]) { find_every_with_older(options) }
128
138
  elsif options.include?(:with_older)
129
139
  find_every_with_older(options)
130
140
  else