activewarehouse 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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