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.
- 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
|