activewarehouse 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +27 -14
- data/Rakefile +16 -5
- data/doc/references.txt +4 -0
- data/generators/bridge/templates/migration.rb +9 -2
- data/generators/bridge/templates/unit_test.rb +8 -0
- data/generators/date_dimension/USAGE +1 -0
- data/generators/date_dimension/date_dimension_generator.rb +16 -0
- data/generators/date_dimension/templates/fixture.yml +5 -0
- data/generators/date_dimension/templates/migration.rb +31 -0
- data/generators/date_dimension/templates/model.rb +3 -0
- data/generators/date_dimension/templates/unit_test.rb +8 -0
- data/generators/dimension/templates/migration.rb +1 -10
- data/generators/dimension_view/dimension_view_generator.rb +2 -2
- data/generators/dimension_view/templates/migration.rb +8 -2
- data/generators/fact/templates/migration.rb +2 -0
- data/generators/time_dimension/USAGE +1 -0
- data/generators/time_dimension/templates/fixture.yml +5 -0
- data/generators/time_dimension/templates/migration.rb +12 -0
- data/generators/time_dimension/templates/model.rb +3 -0
- data/generators/time_dimension/templates/unit_test.rb +8 -0
- data/generators/time_dimension/time_dimension_generator.rb +14 -0
- data/lib/active_warehouse.rb +13 -2
- data/lib/active_warehouse/aggregate.rb +54 -253
- data/lib/active_warehouse/aggregate/dwarf/node.rb +36 -0
- data/lib/active_warehouse/aggregate/dwarf_aggregate.rb +369 -0
- data/lib/active_warehouse/aggregate/dwarf_common.rb +44 -0
- data/lib/active_warehouse/aggregate/dwarf_printer.rb +34 -0
- data/lib/active_warehouse/aggregate/no_aggregate.rb +194 -0
- data/lib/active_warehouse/aggregate/pid_aggregate.rb +29 -0
- data/lib/active_warehouse/aggregate/pipelined_rolap_aggregate.rb +129 -0
- data/lib/active_warehouse/aggregate/rolap_aggregate.rb +181 -0
- data/lib/active_warehouse/aggregate/rolap_common.rb +89 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_1.sql +12 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_10.sql +7166 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_11.sql +14334 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_12.sql +28670 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_13.sql +57342 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_2.sql +26 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_3.sql +54 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_4.sql +110 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_5.sql +222 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_6.sql +446 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_7.sql +894 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_8.sql +1790 -0
- data/lib/active_warehouse/aggregate/templates/pipelined_rollup_9.sql +3582 -0
- data/lib/active_warehouse/aggregate_field.rb +49 -0
- data/lib/active_warehouse/{dimension/bridge.rb → bridge.rb} +7 -3
- data/lib/active_warehouse/bridge/hierarchy_bridge.rb +46 -0
- data/lib/active_warehouse/builder.rb +2 -1
- data/lib/active_warehouse/builder/date_dimension_builder.rb +5 -2
- data/lib/active_warehouse/builder/generator/generator.rb +13 -0
- data/lib/active_warehouse/builder/generator/name_generator.rb +20 -0
- data/lib/active_warehouse/builder/generator/paragraph_generator.rb +11 -0
- data/lib/active_warehouse/builder/random_data_builder.rb +21 -11
- data/lib/active_warehouse/builder/test_data_builder.rb +54 -0
- data/lib/active_warehouse/calculated_field.rb +27 -0
- data/lib/active_warehouse/compat/compat.rb +4 -4
- data/lib/active_warehouse/cube.rb +126 -225
- data/lib/active_warehouse/cube_query_result.rb +69 -0
- data/lib/active_warehouse/dimension.rb +64 -29
- data/lib/active_warehouse/dimension/date_dimension.rb +15 -0
- data/lib/active_warehouse/dimension/dimension_reflection.rb +21 -0
- data/lib/active_warehouse/dimension/dimension_view.rb +17 -2
- data/lib/active_warehouse/dimension/hierarchical_dimension.rb +43 -5
- data/lib/active_warehouse/dimension/slowly_changing_dimension.rb +22 -12
- data/lib/active_warehouse/fact.rb +119 -40
- data/lib/active_warehouse/field.rb +74 -0
- data/lib/active_warehouse/ordered_hash.rb +34 -0
- data/lib/active_warehouse/prejoin_fact.rb +97 -0
- data/lib/active_warehouse/report/abstract_report.rb +40 -14
- data/lib/active_warehouse/report/chart_report.rb +3 -3
- data/lib/active_warehouse/report/table_report.rb +8 -3
- data/lib/active_warehouse/version.rb +1 -1
- data/lib/active_warehouse/view/report_helper.rb +144 -34
- data/tasks/active_warehouse_tasks.rake +28 -10
- 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
|
7
|
-
# are applied to facts. Dimensions are the primary
|
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
|
24
|
-
# level being queried in the following method
|
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
|
50
|
-
# if no hierarchy is specified when
|
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
|
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
|
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
|
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
|
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.
|
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
|
116
|
-
#
|
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)
|
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
|
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
|
135
|
-
#
|
136
|
-
#
|
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
|
139
|
-
# this would be each day)
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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/
|
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 <
|
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
|
-
|
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.
|
47
|
-
:conditions => [
|
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.
|
55
|
-
:conditions => [
|
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
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
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 << :
|
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} =
|
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
|
115
|
+
def with_valid_during_scope(valid_during, &block)
|
116
116
|
with_scope({:find => {:conditions =>
|
117
|
-
["#{effective_date_attribute}
|
118
|
-
|
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?(:
|
127
|
-
|
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
|