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
@@ -1,16 +1,15 @@
1
1
  module ActiveWarehouse #:nodoc
2
- # Facts represent business measures. A row in a fact table corresponds to set of measurements in a particular
3
- # granularity along with the foreign keys connecting the fact to various dimensions. All measurements in a fact
2
+
3
+ # Facts represent business measures. A row in a fact table corresponds to set
4
+ # of measurements in a particular
5
+ # granularity along with the foreign keys connecting the fact to various
6
+ # dimensions. All measurements in a fact
4
7
  # table must be at the same grain.
5
8
  class Fact < ActiveRecord::Base
6
9
  class << self
7
- # Array of aggregate field names
10
+ # Array of AggregateField instances
8
11
  attr_accessor :aggregate_fields
9
12
 
10
- # Hash of aggregate field options, where the key is the field name
11
- # and the value is the Hash of options for that aggregate.
12
- attr_accessor :aggregate_field_options
13
-
14
13
  # Array of calculated field names
15
14
  attr_accessor :calculated_fields
16
15
 
@@ -18,6 +17,42 @@ module ActiveWarehouse #:nodoc
18
17
  # and the value is the Hash of options for that calculated field.
19
18
  attr_accessor :calculated_field_options
20
19
 
20
+ # Array of belongs_to +Reflection+ instances that represent the
21
+ # dimensions for this fact.
22
+ attr_accessor :dimension_relationships
23
+
24
+ # Acts as an alias for +belongs_to+, yet marks this relationship
25
+ # as a dimension. You must call +dimension+ instead of +belongs_to+.
26
+ # Accepts same options as +belongs_to+.
27
+ def dimension(association_id, options = {})
28
+ options[:class_name] ||= "#{association_id}Dimension".classify
29
+ options[:foreign_key] ||= "#{association_id}_id"
30
+ slowly_changing_over = options.delete(:slowly_changing)
31
+ belongs_to association_id, options
32
+ dimension_relationship = reflections[association_id]
33
+
34
+ if slowly_changing_over
35
+ if !dimensions.include?(slowly_changing_over)
36
+ raise "No dimension specified with name '#{slowly_changing_over}' in fact '#{self.name}', specify it first with dimension macro"
37
+ end
38
+ dimension_relationship.slowly_changing_over = dimension_relationships[slowly_changing_over]
39
+ end
40
+
41
+ dimension_relationships[association_id] = dimension_relationship
42
+ end
43
+
44
+ # returns the dimension name (as specified in the dimension macro)
45
+ # which the specified +dimension_name+ is slowly changing over
46
+ def slowly_changes_over_name(dimension_name)
47
+ dimension_relationships[dimension_name].slowly_changing_over.name
48
+ end
49
+
50
+ # returns the Class for the dimension which the specified
51
+ # +dimension_name+ is slowly changing over
52
+ def slowly_changes_over_class(dimension_name)
53
+ dimension_class(slowly_changes_over_name(dimension_name))
54
+ end
55
+
21
56
  # Return a list of dimensions for this fact.
22
57
  #
23
58
  # Example:
@@ -30,27 +65,13 @@ module ActiveWarehouse #:nodoc
30
65
  #
31
66
  # Calling SalesFact.dimensions would return the list: [:date, :region]
32
67
  def dimensions
33
- foreign_key_columns.collect { |c| c.name.gsub(/_id/, '').to_sym }
68
+ dimension_relationships.collect { |k,v| k }
34
69
  end
35
70
 
36
- # Get all of the Column objects representing foreign key columns
37
- #
38
- # Example:
39
- #
40
- # sales_fact
41
- # date_id
42
- # region_id
43
- # sales_amount
44
- # number_items_sold
45
- #
46
- # Calling SalesFact.foreign_key_columns would return a list of column objects containing
47
- # the date column and the region column.
48
- def foreign_key_columns
49
- fk_columns = []
50
- columns.each do |column|
51
- fk_columns << column if column.name =~ /(.*)_id/
52
- end
53
- fk_columns
71
+ # Returns the dimension class, given a dimension name from this fact.
72
+ # Must appear as a registered dimension relationship.
73
+ def dimension_class(dimension_name)
74
+ dimension_relationships[dimension_name.to_sym].class_name.constantize
54
75
  end
55
76
 
56
77
  # Get the time when the fact source file was last modified
@@ -79,21 +100,41 @@ module ActiveWarehouse #:nodoc
79
100
 
80
101
  # Get the fact class for the specified value. The fact parameter may be a class,
81
102
  # String or Symbol.
82
- def to_fact(dimension)
83
- return dimension if dimension.is_a?(Class) and dimension.superclass == Fact
84
- return class_for_name(dimension)
103
+ def to_fact(fact_name)
104
+ return fact_name if fact_name.is_a?(Class) and fact_name.superclass == Fact
105
+ return class_for_name(fact_name)
106
+ end
107
+
108
+ # Return the foreign key that the fact uses to relate back to the specified
109
+ # dimension. This is found using the dimension_relationships hash.
110
+ def foreign_key_for(dimension_name)
111
+ dimension_relationships[dimension_name].primary_key_name
85
112
  end
86
113
 
87
114
  # Define an aggregate. Also aliased from aggregate()
88
115
  # * <tt>field</tt>: The field name
89
116
  # * <tt>options</tt>: A hash of options for the aggregate
90
117
  def define_aggregate(field, options={})
91
- aggregate_fields << field
118
+ if columns_hash[field.to_s].nil?
119
+ raise ArgumentError, "Field #{field} does not exist in table #{table_name}"
120
+ end
92
121
  options[:type] ||= :sum
93
- aggregate_field_options[field] = options
122
+
123
+ aggregate_field = AggregateField.new(self, columns_hash[field.to_s],
124
+ options[:type], options)
125
+ aggregate_fields << aggregate_field
94
126
  end
95
127
  alias :aggregate :define_aggregate
96
128
 
129
+ # Define prejoined fields from a dimension of the fact. Also aliased
130
+ # from prejoin()
131
+ # * <tt>field</tt>: A hash with the key of dimension and an array
132
+ # of attributes from the dimension as value
133
+ def define_prejoin(field)
134
+ prejoined_fields.merge!(field)
135
+ end
136
+ alias :prejoin :define_prejoin
137
+
97
138
  # Define a calculated field
98
139
  # * <tt>field</tt>: The field name
99
140
  # * <tt>options</tt>: An options hash
@@ -102,9 +143,16 @@ module ActiveWarehouse #:nodoc
102
143
  #
103
144
  # Example: calculated_field (:gross_margin) { |r| r.gross_profit_dollar_amount / r.sales_dollar_amount}
104
145
  def calculated_field(field, options={}, &block)
105
- calculated_fields << field
106
- options[:block] = block
107
- calculated_field_options[field] = options
146
+ calculated_fields << CalculatedField.new(self, field, options[:type], options, &block)
147
+ end
148
+
149
+ # Returns true if this fact has at least one fact that is semiadditive,
150
+ # or false
151
+ def has_semiadditive_fact?
152
+ aggregate_fields.each do |field|
153
+ return true if field.is_semiadditive?
154
+ end
155
+ return false
108
156
  end
109
157
 
110
158
  # Get a list of all calculated fields
@@ -112,9 +160,9 @@ module ActiveWarehouse #:nodoc
112
160
  @calculated_field ||= []
113
161
  end
114
162
 
115
- # Get a hash of all calculated field options
116
- def calculated_field_options
117
- @calculated_field_options ||= {}
163
+ # Get the CalculatedField instance for the specified name
164
+ def calculated_field_for_name(name)
165
+ calculated_fields.find {|f| f.name.to_s == name.to_s}
118
166
  end
119
167
 
120
168
  # Get a list of all aggregate fields
@@ -122,10 +170,41 @@ module ActiveWarehouse #:nodoc
122
170
  @aggregate_fields ||= []
123
171
  end
124
172
 
125
- # Get a hash of all aggregate field options
126
- def aggregate_field_options
127
- @aggregate_field_options ||= {}
173
+ # Get the AggregateField instance for the specified name.
174
+ def aggregate_field_for_name(name)
175
+ aggregate_fields.find {|f| f.name.to_s == name.to_s}
176
+ end
177
+
178
+ # Get the field instance for the specified name. Looks in aggregate fields first, then
179
+ # calculated fields
180
+ def field_for_name(name)
181
+ field = aggregate_fields.find {|f| f.name.to_s == name.to_s}
182
+ field = calculated_fields.find {|f| f.name.to_s == name.to_s} unless field
183
+ field
184
+ end
185
+
186
+ # The table name to use for the prejoined fact table
187
+ def prejoined_table_name
188
+ "prejoined_#{table_name}"
189
+ end
190
+
191
+ # Get the hash of all prejoined fields
192
+ def prejoined_fields
193
+ @prejoined_fields ||= {}
194
+ end
195
+
196
+ def dimension_relationships
197
+ @dimension_relationships ||= OrderedHash.new
198
+ end
199
+
200
+ def prejoin_fact
201
+ @prejoin_fact ||= ActiveWarehouse::PrejoinFact.new(self)
202
+ end
203
+
204
+ def populate
205
+ prejoin_fact.populate
128
206
  end
207
+
129
208
  end
130
209
  end
131
210
  end
@@ -0,0 +1,74 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ # Encapsulates a field.
3
+ class Field
4
+ # The owning class which is either a Fact or Dimension
5
+ attr_reader :owning_class
6
+
7
+ # The field name
8
+ attr_reader :name
9
+
10
+ # The field type
11
+ attr_reader :type
12
+
13
+ attr_accessor :limit
14
+ attr_accessor :scale
15
+ attr_accessor :precision
16
+
17
+ # A Hash of options for the field
18
+ attr_reader :field_options
19
+
20
+ # +owning_class+ is the class of the table, either Fact or Dimension, that
21
+ # this field is found in. Must somehow subclass ActiveRecord::Base
22
+ # +name+ is the name of this field.
23
+ # +field_options+ is a hash of raw options from the original definition.
24
+ # Options can include :label => a column alias or label for this field,
25
+ # :table_alias for a table alias (useful for building queries)
26
+ def initialize(owning_class, name, type, field_options = {})
27
+ @owning_class = owning_class
28
+ @name = name
29
+ @type = type
30
+ @field_options = field_options
31
+ @label = field_options[:label]
32
+ @table_alias = field_options[:table_alias]
33
+ end
34
+
35
+ # returns the :label set in field_options, or from_table_name+'_'+name.
36
+ # Unless you have table_alias specified, then label will return table_alias+'_'+name.
37
+ # The default label can exceed database limits, so use :label to override.
38
+ def label
39
+ @label ? @label : "#{table_alias || from_table_name}_#{name}"
40
+ end
41
+
42
+ # returns name of this field, matches name of the column
43
+ def name
44
+ @name
45
+ end
46
+
47
+ # returns rails specific column type, e.g. :float or :string
48
+ def column_type
49
+ @type
50
+ end
51
+
52
+ # convert the label into something we can use in a table.
53
+ # i.e., 'Sum of Transactions' becomes 'sum_of_transactions'
54
+ def label_for_table
55
+ label.gsub(/ /, '_').downcase
56
+ end
57
+
58
+ # returns the table name that has this fact column
59
+ def from_table_name
60
+ owning_class.table_name
61
+ end
62
+
63
+ # returns a table alias or if none set just the table name
64
+ def table_alias
65
+ @table_alias || from_table_name
66
+ end
67
+
68
+ # Get a display string for the field. Delegates to label.
69
+ def to_s
70
+ label
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,34 @@
1
+ module ActiveWarehouse
2
+ # Simple ordered hash implementation
3
+ class OrderedHash < Hash
4
+ alias_method :store, :[]=
5
+ alias_method :each_pair, :each
6
+
7
+ def initialize
8
+ @keys = []
9
+ end
10
+
11
+ def []=(key, val)
12
+ @keys << key
13
+ super
14
+ end
15
+
16
+ def delete(key)
17
+ @keys.delete(key)
18
+ super
19
+ end
20
+
21
+ def each
22
+ @keys.each { |k| yield k, self[k] }
23
+ end
24
+
25
+ def each_key
26
+ @keys.each { |k| yield k }
27
+ end
28
+
29
+ def each_value
30
+ @keys.each { |k| yield self[k] }
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,97 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ # Class that supports prejoining a fact table with dimensions. This is useful if you need
3
+ # to list facts along with some or all of their detail information.
4
+ class PrejoinFact
5
+ # The fact class that this engine instance is connected to
6
+ attr_accessor :fact_class
7
+
8
+ delegate :prejoined_table_name,
9
+ :connection,
10
+ :prejoined_fields,
11
+ :dimension_relationships,
12
+ :dimension_class,
13
+ :table_name,
14
+ :columns, :to => :fact_class
15
+
16
+ # Initialize the engine instance
17
+ def initialize(fact_class)
18
+ @fact_class = fact_class
19
+ end
20
+
21
+ # Populate the prejoined fact table.
22
+ def populate(options={})
23
+ populate_prejoined_fact_table(options)
24
+ end
25
+
26
+ protected
27
+ # Drop the storage table
28
+ def drop_prejoin_fact_table
29
+ connection.drop_table(prejoined_table_name) if connection.tables.include?(prejoined_table_name)
30
+ end
31
+
32
+ # Get foreign key names that are excluded.
33
+ def excluded_foreign_key_names
34
+ excluded_dimension_relations = prejoined_fields.keys.collect {|k| dimension_relationships[k]}
35
+ excluded_dimension_relations.collect {|r| r.primary_key_name}
36
+ end
37
+
38
+ # Construct the prejoined fact table.
39
+ def create_prejoined_fact_table(options={})
40
+ connection.transaction {
41
+ drop_prejoin_fact_table
42
+
43
+ connection.create_table(prejoined_table_name, :id => false) do |t|
44
+ # get all columns except the foreign_key columns for prejoined dimensions
45
+ columns.each do |c|
46
+ t.column(c.name, c.type) unless excluded_foreign_key_names.include?(c.name)
47
+ end
48
+ #prejoined_columns
49
+ prejoined_fields.each_pair do |key, value|
50
+ dclass = dimension_class(key)
51
+ dclass.columns.each do |c|
52
+ t.column(c.name, c.type) if value.include?(c.name.to_sym)
53
+ end
54
+ end
55
+ end
56
+ }
57
+ end
58
+
59
+ # Populate the prejoined fact table.
60
+ def populate_prejoined_fact_table(options={})
61
+ fact_columns_string = columns.collect {|c|
62
+ "#{table_name}." + c.name unless excluded_foreign_key_names.include?(c.name)
63
+ }.compact.join(",\n")
64
+
65
+ prejoined_columns = []
66
+
67
+ tables_and_joins = "#{table_name}"
68
+
69
+ prejoined_fields.each_pair do |key, value|
70
+ dimension = dimension_class(key)
71
+ tables_and_joins += "\nJOIN #{dimension.table_name} as #{key}"
72
+ tables_and_joins += "\n ON #{table_name}.#{dimension_relationships[key].primary_key_name} = "
73
+ tables_and_joins += "#{key}.#{dimension.primary_key}"
74
+ prejoined_columns << value.collect {|v| "#{key}." + v.to_s}
75
+ end
76
+
77
+ if connection.support_select_into_table?
78
+ drop_prejoin_fact_table
79
+ sql = <<-SQL
80
+ SELECT #{fact_columns_string},
81
+ #{prejoined_columns.join(",\n")}
82
+ INTO #{prejoined_table_name}
83
+ FROM #{tables_and_joins}
84
+ SQL
85
+ else
86
+ create_prejoined_fact_table(options)
87
+ sql = <<-SQL
88
+ INSERT INTO #{prejoined_table_name}
89
+ SELECT #{fact_columns_string},
90
+ #{prejoined_columns.join(",\n")}
91
+ FROM #{tables_and_joins}
92
+ SQL
93
+ end
94
+ connection.transaction { connection.execute(sql) }
95
+ end
96
+ end
97
+ end
@@ -1,12 +1,25 @@
1
- module ActiveWarehouse
2
- module Report
1
+ module ActiveWarehouse #:nodoc:
2
+ module Report #:nodoc:
3
+ # Base module for reports.
3
4
  module AbstractReport
4
5
 
6
+ # Array of parameters which will be passed
5
7
  attr_accessor :pass_params
8
+
9
+ # A Hash of level names mapped to a method that is used to filter the available
10
+ # column values
11
+ attr_accessor :column_filters
12
+
13
+ # A Hash of level names mapped to a method that is used to filter the available
14
+ # row values
15
+ attr_accessor :row_filters
16
+
17
+ # An optional conditions String
18
+ attr_accessor :conditions
6
19
 
7
20
  # Set the cube name
8
21
  def cube_name=(name)
9
- self['cube_name'] = name
22
+ write_attribute(:cube_name, name)
10
23
  @cube = nil
11
24
  end
12
25
 
@@ -29,12 +42,19 @@ module ActiveWarehouse
29
42
  @column_dimension_class ||= ActiveWarehouse::Dimension.class_name(self.column_dimension_name).constantize
30
43
  end
31
44
 
45
+ # Get the column hierarchy. Uses the first hierarchy in the column dimension if not specified
32
46
  def column_hierarchy
33
- self['column_hierarchy'] || column_dimension_class.hierarchies.first
47
+ ch = read_attribute(:column_hierarchy)
48
+ if ch.nil? || ch == 'NULL'
49
+ column_dimension_class.hierarchies.first
50
+ else
51
+ ch
52
+ end
34
53
  end
35
54
 
55
+ # Get the column prefix. Returns 'c' if not specified.
36
56
  def column_param_prefix
37
- self['column_param_prefix'] || 'c'
57
+ read_attribute(:column_param_prefix) || 'c'
38
58
  end
39
59
 
40
60
  # Get the row dimension class
@@ -42,28 +62,34 @@ module ActiveWarehouse
42
62
  @row_dimension_class ||= ActiveWarehouse::Dimension.class_name(self.row_dimension_name).constantize
43
63
  end
44
64
 
65
+ # Get the row hierarchy. Uses the first hierarchy in the row dimension if not specified
45
66
  def row_hierarchy
46
- self['row_hierarchy'] || row_dimension_class.hierarchies.first
67
+ read_attribute(:row_hierarchy) || row_dimension_class.hierarchies.first
47
68
  end
48
69
 
70
+ # Get the row parameter prefix. Returns 'r' if not specified.
49
71
  def row_param_prefix
50
- self['row_param_prefix'] || 'r'
72
+ read_attribute(:row_param_prefix) || 'r'
51
73
  end
52
74
 
53
75
  # Get the list of displayed fact attributes. If this value is not specified then all aggregate and calculated
54
76
  # fields will be displayed
55
77
  def fact_attributes
56
- return self['fact_attributes'] if self['fact_attributes']
78
+ return read_attribute(:fact_attributes) if read_attribute(:fact_attributes)
57
79
  fa = []
58
- fact_class.aggregate_fields.each do |name|
59
- fa << name
60
- end
61
- fact_class.calculated_fields.each do |name|
62
- fa << name
63
- end
80
+ fact_class.aggregate_fields.each { |field| fa << field }
81
+ fact_class.calculated_fields.each { |field| fa << field }
64
82
  fa
65
83
  end
66
84
 
85
+ def column_filters
86
+ @column_filters ||= {}
87
+ end
88
+
89
+ def row_filters
90
+ @row_filters ||= {}
91
+ end
92
+
67
93
  def pass_params
68
94
  @pass_params ||= []
69
95
  end