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