mainej-activewarehouse 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/activewarehouse/README +99 -0
  2. data/activewarehouse/Rakefile +165 -0
  3. data/activewarehouse/TODO +4 -0
  4. data/activewarehouse/db/migrations/001_create_table_reports.rb +28 -0
  5. data/activewarehouse/doc/references.txt +4 -0
  6. data/activewarehouse/generators/bridge/USAGE +1 -0
  7. data/activewarehouse/generators/bridge/bridge_generator.rb +46 -0
  8. data/activewarehouse/generators/bridge/templates/fixture.yml +5 -0
  9. data/activewarehouse/generators/bridge/templates/migration.rb +27 -0
  10. data/activewarehouse/generators/bridge/templates/model.rb +3 -0
  11. data/activewarehouse/generators/bridge/templates/unit_test.rb +8 -0
  12. data/activewarehouse/generators/cube/USAGE +1 -0
  13. data/activewarehouse/generators/cube/cube_generator.rb +28 -0
  14. data/activewarehouse/generators/cube/templates/model.rb +3 -0
  15. data/activewarehouse/generators/cube/templates/unit_test.rb +8 -0
  16. data/activewarehouse/generators/date_dimension/USAGE +1 -0
  17. data/activewarehouse/generators/date_dimension/date_dimension_generator.rb +16 -0
  18. data/activewarehouse/generators/date_dimension/templates/fixture.yml +5 -0
  19. data/activewarehouse/generators/date_dimension/templates/migration.rb +31 -0
  20. data/activewarehouse/generators/date_dimension/templates/model.rb +3 -0
  21. data/activewarehouse/generators/date_dimension/templates/unit_test.rb +8 -0
  22. data/activewarehouse/generators/dimension/USAGE +1 -0
  23. data/activewarehouse/generators/dimension/dimension_generator.rb +46 -0
  24. data/activewarehouse/generators/dimension/templates/fixture.yml +5 -0
  25. data/activewarehouse/generators/dimension/templates/migration.rb +11 -0
  26. data/activewarehouse/generators/dimension/templates/model.rb +3 -0
  27. data/activewarehouse/generators/dimension/templates/unit_test.rb +8 -0
  28. data/activewarehouse/generators/dimension_view/USAGE +1 -0
  29. data/activewarehouse/generators/dimension_view/dimension_view_generator.rb +62 -0
  30. data/activewarehouse/generators/dimension_view/templates/migration.rb +17 -0
  31. data/activewarehouse/generators/dimension_view/templates/model.rb +3 -0
  32. data/activewarehouse/generators/dimension_view/templates/unit_test.rb +10 -0
  33. data/activewarehouse/generators/fact/USAGE +1 -0
  34. data/activewarehouse/generators/fact/fact_generator.rb +46 -0
  35. data/activewarehouse/generators/fact/templates/fixture.yml +5 -0
  36. data/activewarehouse/generators/fact/templates/migration.rb +13 -0
  37. data/activewarehouse/generators/fact/templates/model.rb +3 -0
  38. data/activewarehouse/generators/fact/templates/unit_test.rb +10 -0
  39. data/activewarehouse/generators/time_dimension/USAGE +1 -0
  40. data/activewarehouse/generators/time_dimension/templates/fixture.yml +5 -0
  41. data/activewarehouse/generators/time_dimension/templates/migration.rb +12 -0
  42. data/activewarehouse/generators/time_dimension/templates/model.rb +3 -0
  43. data/activewarehouse/generators/time_dimension/templates/unit_test.rb +8 -0
  44. data/activewarehouse/generators/time_dimension/time_dimension_generator.rb +14 -0
  45. data/activewarehouse/init.rb +1 -0
  46. data/activewarehouse/install.rb +5 -0
  47. data/activewarehouse/lib/active_warehouse.rb +91 -0
  48. data/activewarehouse/lib/active_warehouse/aggregate.rb +75 -0
  49. data/activewarehouse/lib/active_warehouse/aggregate/dwarf_aggregate.rb +369 -0
  50. data/activewarehouse/lib/active_warehouse/aggregate/dwarf_common.rb +44 -0
  51. data/activewarehouse/lib/active_warehouse/aggregate/dwarf_printer.rb +34 -0
  52. data/activewarehouse/lib/active_warehouse/aggregate/no_aggregate.rb +212 -0
  53. data/activewarehouse/lib/active_warehouse/aggregate/pid_aggregate.rb +29 -0
  54. data/activewarehouse/lib/active_warehouse/aggregate_field.rb +59 -0
  55. data/activewarehouse/lib/active_warehouse/bridge.rb +19 -0
  56. data/activewarehouse/lib/active_warehouse/bridge/hierarchy_bridge.rb +46 -0
  57. data/activewarehouse/lib/active_warehouse/builder.rb +3 -0
  58. data/activewarehouse/lib/active_warehouse/builder/date_dimension_builder.rb +91 -0
  59. data/activewarehouse/lib/active_warehouse/builder/generator/generator.rb +13 -0
  60. data/activewarehouse/lib/active_warehouse/builder/generator/name_generator.rb +20 -0
  61. data/activewarehouse/lib/active_warehouse/builder/generator/paragraph_generator.rb +11 -0
  62. data/activewarehouse/lib/active_warehouse/builder/random_data_builder.rb +239 -0
  63. data/activewarehouse/lib/active_warehouse/builder/test_data_builder.rb +54 -0
  64. data/activewarehouse/lib/active_warehouse/calculated_field.rb +27 -0
  65. data/activewarehouse/lib/active_warehouse/compat/compat.rb +49 -0
  66. data/activewarehouse/lib/active_warehouse/core_ext.rb +1 -0
  67. data/activewarehouse/lib/active_warehouse/core_ext/time.rb +5 -0
  68. data/activewarehouse/lib/active_warehouse/core_ext/time/calculations.rb +40 -0
  69. data/activewarehouse/lib/active_warehouse/cube.rb +235 -0
  70. data/activewarehouse/lib/active_warehouse/cube_query_result.rb +69 -0
  71. data/activewarehouse/lib/active_warehouse/dimension.rb +329 -0
  72. data/activewarehouse/lib/active_warehouse/dimension/date_dimension.rb +15 -0
  73. data/activewarehouse/lib/active_warehouse/dimension/dimension_reflection.rb +21 -0
  74. data/activewarehouse/lib/active_warehouse/dimension/dimension_view.rb +27 -0
  75. data/activewarehouse/lib/active_warehouse/dimension/hierarchical_dimension.rb +99 -0
  76. data/activewarehouse/lib/active_warehouse/dimension/slowly_changing_dimension.rb +147 -0
  77. data/activewarehouse/lib/active_warehouse/fact.rb +239 -0
  78. data/activewarehouse/lib/active_warehouse/field.rb +74 -0
  79. data/activewarehouse/lib/active_warehouse/migrations.rb +64 -0
  80. data/activewarehouse/lib/active_warehouse/ordered_hash.rb +34 -0
  81. data/activewarehouse/lib/active_warehouse/prejoin_fact.rb +97 -0
  82. data/activewarehouse/lib/active_warehouse/report.rb +7 -0
  83. data/activewarehouse/lib/active_warehouse/report/abstract_report.rb +149 -0
  84. data/activewarehouse/lib/active_warehouse/report/chart_report.rb +9 -0
  85. data/activewarehouse/lib/active_warehouse/report/data_cell.rb +21 -0
  86. data/activewarehouse/lib/active_warehouse/report/data_column.rb +19 -0
  87. data/activewarehouse/lib/active_warehouse/report/data_row.rb +15 -0
  88. data/activewarehouse/lib/active_warehouse/report/dimension.rb +58 -0
  89. data/activewarehouse/lib/active_warehouse/report/table_report.rb +38 -0
  90. data/activewarehouse/lib/active_warehouse/version.rb +9 -0
  91. data/activewarehouse/lib/active_warehouse/view.rb +9 -0
  92. data/activewarehouse/lib/active_warehouse/view/crumb.rb +64 -0
  93. data/activewarehouse/lib/active_warehouse/view/report_helper.rb +98 -0
  94. data/activewarehouse/lib/active_warehouse/view/table_view.rb +134 -0
  95. data/activewarehouse/lib/active_warehouse/view/yui_adapter.rb +68 -0
  96. data/activewarehouse/tasks/active_warehouse_tasks.rake +122 -0
  97. metadata +237 -0
@@ -0,0 +1,34 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ module Aggregate #:nodoc:
3
+ # Dwarf support class that prints a representation of the Dwarf
4
+ class DwarfPrinter
5
+ # Print the specified node at the given depth.
6
+ def self.print_node(node, depth=0, recurse=true)
7
+ #puts "printing node #{node.index}"
8
+ cells = node.cells.collect { |c| cell_to_string(c)}.join('|')
9
+
10
+ parent_node = node.parent ? "#{cell_to_string(node.parent)}:" : ''
11
+ puts "#{node.index}=#{' '*depth}#{parent_node}[#{cells}|#{all_cell_to_string(node.all_cell)}]"
12
+ if !node.leaf?
13
+ print_node(node.all_cell.child, depth + 1, false) if node.all_cell
14
+ end
15
+ if recurse
16
+ node.children.each { |child| print_node(child, depth+1) }
17
+ end
18
+ end
19
+
20
+ def self.cell_to_string(cell)
21
+ # a new String object must be created here, otherwise to_s returns a reference
22
+ # to the same String object each time and thus the value will be appended each time
23
+ # which is not what I want
24
+ s = String.new(cell.key.to_s)
25
+ s << " #{cell.value.join(',')}" if cell.node.leaf?
26
+ s
27
+ end
28
+
29
+ def self.all_cell_to_string(cell)
30
+ cell ? (cell.value ? cell.value.inspect : '') : ''
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,212 @@
1
+ require 'set'
2
+
3
+ module ActiveWarehouse #:nodoc:
4
+ module Aggregate #:nodoc:
5
+ # An aggregate which goes directly to the fact and dimensions to answer questions
6
+ class NoAggregate < Aggregate
7
+ # Populate the aggregate (in this case it is a no-op implementation)
8
+ def populate
9
+ # do nothing
10
+ end
11
+
12
+ # Query the aggregate
13
+ # def query(column_dimension_name, column_hierarchy_name,
14
+ # row_dimension_name, row_hierarchy_name, conditions=nil,
15
+ # cstage=0, rstage=0, filters={})
16
+
17
+ # Query the aggregate
18
+ def query(*args)
19
+ options = parse_query_args(*args)
20
+
21
+ column_dimension_name = options[:column_dimension_name]
22
+ column_hierarchy_name = options[:column_hierarchy_name]
23
+ row_dimension_name = options[:row_dimension_name]
24
+ row_hierarchy_name = options[:row_hierarchy_name]
25
+ conditions = options[:conditions]
26
+ cstage = options[:cstage] || 0
27
+ rstage = options[:rstage] || 0
28
+ filters = options[:filters] || {}
29
+
30
+ fact_class = cube_class.fact_class
31
+ column_dimension = fact_class.dimension_class(column_dimension_name)
32
+ column_hierarchy = column_dimension.hierarchy(column_hierarchy_name)
33
+ row_dimension = fact_class.dimension_class(row_dimension_name)
34
+ row_hierarchy = row_dimension.hierarchy(row_hierarchy_name)
35
+
36
+ used_dimensions = Set.new
37
+ used_dimensions.merge([column_dimension_name, row_dimension_name])
38
+ row_dim_reflection = fact_class.dimension_relationships[row_dimension_name].dependent_dimension_reflections
39
+ used_dimensions.merge(row_dim_reflection.collect{|d| d.name})
40
+ col_dim_reflection = fact_class.dimension_relationships[column_dimension_name].dependent_dimension_reflections
41
+ used_dimensions.merge(col_dim_reflection.collect{|d| d.name})
42
+ filters.each do |k,v|
43
+ used_dimensions << k.split('.')[0]
44
+ end
45
+ if conditions
46
+ cube_class.dimensions.each do |dimension|
47
+ if conditions =~ /#{dimension}\./i
48
+ used_dimensions << dimension
49
+ end
50
+ end
51
+ end
52
+
53
+ # This method assumes at most one dimension is hierarchical dimension
54
+ # in the query params. TODO: need to handle when both row and column
55
+ # are hierarchical dimensions.
56
+ hierarchical_dimension = nil
57
+ hierarchical_dimension_name = nil
58
+ hierarchical_stage = nil
59
+
60
+ if !column_dimension.hierarchical_dimension?
61
+ current_column_name = column_hierarchy[cstage]
62
+ else
63
+ hierarchical_dimension = column_dimension
64
+ hierarchical_dimension_name = column_dimension_name
65
+ hierarchical_stage = cstage
66
+ current_column_name = column_hierarchy[0]
67
+ end
68
+
69
+ if !row_dimension.hierarchical_dimension?
70
+ current_row_name = row_hierarchy[rstage]
71
+ else
72
+ hierarchical_dimension = row_dimension
73
+ hierarchical_dimension_name = row_dimension_name
74
+ hierarchical_stage = rstage
75
+ current_row_name = row_hierarchy[0]
76
+ end
77
+
78
+ fact_columns = cube_class.aggregate_fields(used_dimensions).collect { |c|
79
+ agg_sql = ''
80
+ quoted_label = cube_class.connection.quote_column_name(c.label)
81
+ if hierarchical_dimension and !c.levels_from_parent.empty?
82
+ bridge = hierarchical_dimension.bridge_class
83
+ bridge_table_name = bridge.table_name
84
+ levels_from_parent = bridge.levels_from_parent
85
+ get_all = false
86
+ c.levels_from_parent.each do |level|
87
+ case level
88
+ when :all
89
+ agg_sql += " #{c.strategy_name}(#{c.from_table_name}.#{c.name}) AS #{quoted_label})"
90
+ get_all = true
91
+ when :self
92
+ agg_sql += " #{c.strategy_name}(CASE " if agg_sql.length == 0
93
+ agg_sql += " WHEN #{bridge_table_name}.#{levels_from_parent} = 0 THEN #{c.from_table_name}.#{c.name} \n"
94
+ when Integer
95
+ agg_sql += " #{c.strategy_name}(CASE " if agg_sql.length == 0
96
+ agg_sql += " WHEN #{bridge_table_name}.#{levels_from_parent} = #{level} then #{c.from_table_name}.#{c.name} \n"
97
+ else
98
+ raise ArgumentError, "Each element to :levels_from_parent option must be :all, :self, or Integer"
99
+ end
100
+ end
101
+ agg_sql += " ELSE 0 END) AS #{quoted_label}" unless get_all
102
+ else
103
+ if c.is_distinct?
104
+ agg_sql = " #{c.strategy_name}(distinct #{c.from_table_name}.#{c.name}) AS #{quoted_label}"
105
+ else
106
+ agg_sql = " #{c.strategy_name}(#{c.from_table_name}.#{c.name}) AS #{quoted_label}"
107
+ end
108
+ end
109
+ agg_sql
110
+ }.join(",\n")
111
+
112
+ sql = ''
113
+ sql += "SELECT\n"
114
+ sql += " #{column_dimension_name}.#{current_column_name} as #{column_dimension_name}_1_#{current_column_name},\n"
115
+ sql += " #{row_dimension_name}.#{current_row_name} as #{row_dimension_name}_2_#{current_row_name},\n"
116
+ sql += fact_columns
117
+ sql += "\nFROM\n"
118
+
119
+ sql += " #{fact_class.table_name}"
120
+ cube_class.dimensions_hierarchies.each do |dimension_name, hierarchy_names|
121
+ next if !used_dimensions.include?(dimension_name)
122
+ dimension = fact_class.dimension_class(dimension_name)
123
+ if !dimension.hierarchical_dimension?
124
+ if fact_class.belongs_to_relationship?(dimension_name)
125
+ sql += "\nJOIN #{dimension.table_name} as #{dimension_name}"
126
+ sql += "\n ON #{fact_class.table_name}.#{fact_class.foreign_key_for(dimension_name)} = "
127
+ sql += "#{dimension_name}.#{dimension.primary_key}"
128
+ elsif fact_class.has_and_belongs_to_many_relationship?(dimension_name)
129
+ relationship = fact_class.dimension_relationship(dimension_name)
130
+ sql += "\nJOIN #{relationship.options[:join_table]} as #{dimension_name}_bridge"
131
+ sql += "\n ON #{fact_class.table_name}.#{fact_class.primary_key} = "
132
+ sql += "#{dimension_name}_bridge.#{relationship.options[:foreign_key]}"
133
+ sql += "\nJOIN #{dimension.table_name} as #{dimension_name}"
134
+ sql += "\n ON #{dimension_name}_bridge.#{relationship.options[:association_foreign_key]} = "
135
+ sql += "#{dimension_name}.#{dimension.primary_key}"
136
+ end
137
+ else
138
+ dimension_bridge = dimension.bridge_class
139
+ sql += "\nJOIN #{dimension_bridge.table_name}"
140
+ sql += "\n ON #{fact_class.table_name}.#{fact_class.foreign_key_for(dimension_name)} = "
141
+ sql += "#{dimension_bridge.table_name}.#{dimension.parent_foreign_key}"
142
+ if dimension.slowly_changing_dimension?
143
+ sql += " and (#{dimension_bridge.table_name}.#{dimension_bridge.effective_date} <= "
144
+ sql += "#{fact_class.slowly_changes_over_name(dimension_name)}."
145
+ sql += "#{fact_class.slowly_changes_over_class(dimension_name).sql_date_stamp} "
146
+ sql += "and #{dimension_bridge.table_name}.#{dimension_bridge.expiration_date} >= "
147
+ sql += "#{fact_class.slowly_changes_over_name(dimension_name)}."
148
+ sql += "#{fact_class.slowly_changes_over_class(dimension_name).sql_date_stamp}) "
149
+ end
150
+ sql += "\nJOIN #{dimension.table_name} as #{dimension_name}"
151
+ sql += "\n ON #{dimension_bridge.table_name}.#{dimension.child_foreign_key} = "
152
+ sql += "#{dimension_name}.#{dimension.primary_key}"
153
+ end
154
+ end
155
+
156
+ # build the where clause
157
+ # first add conditions
158
+ where_clause = Array(conditions)
159
+
160
+ # apply filters
161
+ filters.each do |key, value|
162
+ dimension_name, column = key.split('.')
163
+ where_clause << "#{dimension_name}.#{column} = #{cube_class.connection.quote(value)}"
164
+ end
165
+ sql += %Q(\nWHERE\n #{where_clause.join(" AND\n ")} ) if where_clause.length > 0
166
+
167
+ # for hierarchical dimension we need to add where clause in for drill downs
168
+ if !hierarchical_dimension.nil?
169
+ if where_clause.length == 0
170
+ sql += "\n WHERE "
171
+ else
172
+ sql += " \n AND "
173
+ end
174
+ sql += "\n #{hierarchical_dimension_name}.#{hierarchical_dimension.primary_key} IN ( "
175
+ sql += "\n SELECT #{hierarchical_dimension.parent_foreign_key} FROM #{hierarchical_dimension.bridge_class.table_name} "
176
+ if hierarchical_stage == 0
177
+ sql += "\n WHERE #{hierarchical_dimension.bridge_class.top_flag} = #{connection.send(:quote, hierarchical_dimension.bridge_class.top_flag_value)})"
178
+ else
179
+ sql += "\n WHERE #{hierarchical_dimension.child_foreign_key} = #{hierarchical_stage} AND #{hierarchical_dimension.levels_from_parent} = 1)"
180
+ end
181
+ end
182
+
183
+ sql += "\nGROUP BY\n"
184
+ sql += " #{column_dimension_name}.#{current_column_name},\n"
185
+ sql += " #{row_dimension_name}.#{current_row_name}"
186
+
187
+ if options[:order]
188
+ order_by = options[:order]
189
+ order_by = [order_by] if order_by.is_a?(String)
190
+ order_by.collect!{ |v| cube_class.connection.quote_column_name(order_by) }
191
+ sql += %Q(\nORDER BY\n #{order_by.join(",\n")})
192
+ end
193
+
194
+ if options[:return] == :sql
195
+ sql
196
+ else
197
+ result = ActiveWarehouse::CubeQueryResult.new(
198
+ cube_class.aggregate_fields(used_dimensions)
199
+ )
200
+
201
+ cube_class.connection.select_all(sql).each do |row|
202
+ result.add_data(row.delete("#{row_dimension_name}_2_#{current_row_name}"),
203
+ row.delete("#{column_dimension_name}_1_#{current_column_name}"),
204
+ row) # the rest of the members of row are the fact columns
205
+ end
206
+
207
+ result
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,29 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ module Aggregate #:nodoc:
3
+ # Implementation of a Partitioning and Inserting Dwarf algorithm as defined
4
+ # in http://www.zju.edu.cn/jzus/2005/A0506/A050608.pdf
5
+ class PidAggregate < Aggregate
6
+ include DwarfCommon
7
+
8
+ # Initialize the aggregate
9
+ def initialize(cube_class)
10
+ super
11
+ end
12
+
13
+ # Populate the aggregate
14
+ def populate
15
+ create_dwarf_cube(sorted_facts)
16
+ end
17
+
18
+ # Query the aggregate
19
+ def query(*args)
20
+ options = parse_query_args(*args)
21
+ end
22
+
23
+ def create_dwarf_cube(sorted_facts)
24
+
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,59 @@
1
+ module ActiveWarehouse
2
+ # Encapsulates a fact column in a fact table. These fields
3
+ # represent columns that should be aggregated.
4
+ class AggregateField < Field
5
+
6
+ attr_reader :strategy_name
7
+
8
+ # +fact_class+ is the class of the fact table this field is found in.
9
+ # +column_definition+ is the ActiveRecord ColumnDefinition instance for this
10
+ # column.
11
+ # +strategy_name+ is the name of th aggregation strategy to be used, defaults to :sum
12
+ # +field_options+ is a hash of raw options from the original aggregate definition.
13
+ def initialize(fact_class, column_definition, strategy_name = :sum, field_options = {})
14
+ super(fact_class, column_definition.name, column_definition.type, field_options)
15
+ @column_definition = column_definition
16
+ @limit = column_definition.limit
17
+ @scale = column_definition.scale
18
+ @precision = column_definition.precision
19
+ @strategy_name = strategy_name
20
+ end
21
+
22
+ # delegates to owning_class, returns the Fact that has this field
23
+ def fact_class
24
+ owning_class
25
+ end
26
+
27
+ # Returns true if the field is semi-additive
28
+ def is_semiadditive?
29
+ !field_options[:semiadditive].nil?
30
+ end
31
+
32
+ def is_distinct?
33
+ field_options[:distinct] and field_options[:distinct] == true
34
+ end
35
+
36
+ def is_count_distinct?
37
+ @strategy_name == :count and is_distinct?
38
+ end
39
+
40
+ # returns the Dimension that this semiadditive fact is over
41
+ def semiadditive_over
42
+ Dimension.to_dimension(field_options[:semiadditive])
43
+ end
44
+
45
+ # overrides Field.label, prepending the aggregation strategy name to label
46
+ def label
47
+ @label ? @label : "#{super}_#{strategy_name}"
48
+ end
49
+
50
+ def levels_from_parent
51
+ field_options[:levels_from_parent].nil? ? [] : field_options[:levels_from_parent]
52
+ end
53
+
54
+ # Typecast the specified value using the column definition
55
+ def type_cast(value)
56
+ @column_definition.type_cast(value)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveWarehouse #:nodoc
2
+ # Implements a bridge table.
3
+ class Bridge < ActiveRecord::Base
4
+ class << self
5
+ # Get the table name. By default the table name will be the name of the
6
+ # bridge in singular form.
7
+ #
8
+ # Example: DepartmentHierarchyBridge will have a table called
9
+ # department_hierarchy_bridge
10
+ def table_name
11
+ name = self.name.demodulize.underscore
12
+ set_table_name(name)
13
+ name
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ require 'active_warehouse/bridge/hierarchy_bridge'
@@ -0,0 +1,46 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ # Bridge class that models ragged hierarchies.
3
+ class HierarchyBridge < Bridge
4
+ class << self
5
+ def set_levels_from_parent(name)
6
+ @levels_from_parent = name
7
+ end
8
+
9
+ def levels_from_parent
10
+ @levels_from_parent ||= "levels_from_parent"
11
+ end
12
+
13
+ def set_effective_date(name)
14
+ @effective_date = name
15
+ end
16
+
17
+ def effective_date
18
+ @effective_date ||= "effective_date"
19
+ end
20
+
21
+ def set_expiration_date(name)
22
+ @expiration_date = name
23
+ end
24
+
25
+ def expiration_date
26
+ @expiration_date ||= "expiration_date"
27
+ end
28
+
29
+ def set_top_flag(name)
30
+ @top_flag = name
31
+ end
32
+
33
+ def top_flag
34
+ @top_flag ||= "top_flag"
35
+ end
36
+
37
+ def set_top_flag_value(value)
38
+ @top_flag_value = value
39
+ end
40
+
41
+ def top_flag_value
42
+ @top_flag_value ||= 'Y'
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ require 'active_warehouse/builder/date_dimension_builder'
2
+ require 'active_warehouse/builder/random_data_builder'
3
+ require 'active_warehouse/builder/test_data_builder'
@@ -0,0 +1,91 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ module Builder #:nodoc:
3
+ # A builder which will build a data structure which can be used to populate a date dimension using
4
+ # commonly used date dimension columns.
5
+ class DateDimensionBuilder
6
+ # Specify the start date for the first record
7
+ attr_accessor :start_date
8
+
9
+ # Specify the end date for the last record
10
+ attr_accessor :end_date
11
+
12
+ # Define any holiday indicators
13
+ attr_accessor :holiday_indicators
14
+
15
+ # Define the weekday indicators. The default array begins on Sunday and goes to Saturday.
16
+ cattr_accessor :weekday_indicators
17
+ @@weekday_indicators = ['Weekend','Weekday','Weekday','Weekday','Weekday','Weekday','Weekend']
18
+
19
+ # Initialize the builder.
20
+ #
21
+ # * <tt>start_date</tt>: The start date. Defaults to 5 years ago from today.
22
+ # * <tt>end_date</tt>: The end date. Defaults to now.
23
+ def initialize(start_date=Time.now.years_ago(5), end_date=Time.now)
24
+ @start_date = start_date.to_date
25
+ @end_date = end_date.to_date
26
+ @holiday_indicators = []
27
+ end
28
+
29
+ # Returns an array of hashes representing records in the dimension.
30
+ def build(options={})
31
+ (start_date..end_date).map { |date| record_from_date(date) }
32
+ end
33
+
34
+ private
35
+
36
+ # Returns a hash representing a record in the dimension. The values for each record are
37
+ # accessed by name.
38
+ def record_from_date(date)
39
+ time = date.to_time # need methods only available in Time
40
+ record = {}
41
+ record[:date] = time.strftime("%m/%d/%Y")
42
+ record[:full_date_description] = time.strftime("%B %d,%Y")
43
+ record[:day_of_week] = time.strftime("%A")
44
+ #record[:day_number_in_epoch] = time.to_i / 24
45
+ #record[:week_number_in_epoch] = time.to_i / (24 * 7)
46
+ #record[:month_number_in_epoch] = time.to_i / (24 * 7 * 30)
47
+ record[:day_number_in_calendar_month] = time.day
48
+ record[:day_number_in_calendar_year] = time.yday
49
+ record[:day_number_in_fiscal_month] = time.day # should this be different from CY?
50
+ record[:day_number_in_fiscal_year] = time.fiscal_year_yday
51
+ #record[:last_day_in_week_indicator] =
52
+ #record[:last_day_in_month_indicator] =
53
+ #record[:calendar_week_ending_date] =
54
+ record[:calendar_week] = "Week #{time.week}"
55
+ record[:calendar_week_number] = time.week
56
+ record[:calendar_week_number_in_year] = time.week
57
+ record[:calendar_month_name] = time.strftime("%B")
58
+ record[:calendar_month_number] = time.month
59
+ record[:calendar_month_number_in_year] = time.month
60
+ record[:calendar_year_month] = time.strftime("%Y-%m")
61
+ record[:calendar_quarter] = "Q#{time.quarter}"
62
+ record[:calendar_quarter_number] = time.quarter
63
+ record[:calendar_quarter_number_in_year] = time.quarter
64
+ record[:calendar_year_quarter] = "#{time.strftime('%Y')}-#{record[:calendar_quarter]}"
65
+ #record[:calendar_half_year] =
66
+ record[:calendar_year] = "#{time.year}"
67
+ record[:fiscal_week] = "FY Week #{time.fiscal_year_week}"
68
+ record[:fiscal_week_number] = time.fiscal_year_week
69
+ record[:fiscal_week_number_in_year] = time.fiscal_year_week
70
+ record[:fiscal_month] = time.fiscal_year_month
71
+ record[:fiscal_month_number] = time.fiscal_year_month
72
+ record[:fiscal_month_number_in_year] = time.fiscal_year_month
73
+ record[:fiscal_year_month] = "FY#{time.fiscal_year}-" + time.fiscal_year_month.to_s.rjust(2, '0')
74
+ record[:fiscal_quarter] = "FY Q#{time.fiscal_year_quarter}"
75
+ record[:fiscal_year_quarter] = "FY#{time.fiscal_year}-Q#{time.fiscal_year_quarter}"
76
+ record[:fiscal_quarter_number] = time.fiscal_year_quarter
77
+ record[:fiscal_year_quarter_number] = time.fiscal_year_quarter
78
+ #record[:fiscal_half_year] =
79
+ record[:fiscal_year] = "FY#{time.fiscal_year}"
80
+ record[:fiscal_year_number] = time.fiscal_year
81
+ record[:holiday_indicator] = holiday_indicators.include?(date) ? 'Holiday' : 'Nonholiday'
82
+ record[:weekday_indicator] = weekday_indicators[time.wday]
83
+ record[:selling_season] = 'None'
84
+ record[:major_event] = 'None'
85
+ record[:sql_date_stamp] = date
86
+
87
+ record
88
+ end
89
+ end
90
+ end
91
+ end