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,19 @@
1
+ module ActiveWarehouse
2
+ module Report
3
+
4
+ class DataColumn
5
+
6
+ attr_accessor :dimension_value, :fact_attribute, :label
7
+
8
+ def initialize(label, dimension_value, fact_attribute)
9
+ @label = label
10
+ @dimension_value = dimension_value
11
+ @fact_attribute = fact_attribute
12
+ end
13
+
14
+ def key
15
+ "#{dimension_value}_#{fact_attribute.label}".gsub(' ', '_').downcase
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveWarehouse
2
+ module Report
3
+
4
+ class DataRow
5
+
6
+ attr_accessor :cells, :dimension_value
7
+
8
+ def initialize(dimension_value, cells)
9
+ @dimension_value = dimension_value
10
+ @cells = cells
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,58 @@
1
+ module ActiveWarehouse
2
+ module Report
3
+
4
+ class Dimension
5
+
6
+ attr_reader :dimension_class, :name, :hierarchy, :hierarchy_name, :filters, :stage, :hierarchy_length, :hierarchy_level, :param_prefix
7
+
8
+ def initialize(dimension_type, report, params = {})
9
+
10
+ @dimension_class = report.send("#{dimension_type}_dimension_class")
11
+ @name = report.send("#{dimension_type}_dimension_name")
12
+ @hierarchy_name = report.send("#{dimension_type}_hierarchy")
13
+ @filters = report.send("#{dimension_type}_filters")
14
+ @param_prefix = report.send("#{dimension_type}_param_prefix")
15
+ @stage = (params[:stage] || report.send("#{dimension_type}_stage")).to_i
16
+
17
+ @hierarchy = @dimension_class.hierarchy(@hierarchy_name)
18
+ @hierarchy_length = @hierarchy.length
19
+ @hierarchy_level = @hierarchy[@stage]
20
+ @params = params
21
+ end
22
+
23
+ def self.column(report, params = {})
24
+ Dimension.new(:column, report, params)
25
+ end
26
+
27
+ def self.row(report, params = {})
28
+ Dimension.new(:row, report, params)
29
+ end
30
+
31
+ def query_filters
32
+ param_filters = {}
33
+ @params[:ancestors].each do |key, value|
34
+ param_filters["#{name}.#{key}"] = value
35
+ end
36
+ param_filters
37
+ end
38
+
39
+ def values
40
+ filters[hierarchy_level].blank? ? available_values : available_values & filters[hierarchy_level]
41
+ end
42
+
43
+ def ancestors
44
+ (0..stage-1).map do |s|
45
+ @params[:ancestors][hierarchy[s].to_s]
46
+ end.compact
47
+ end
48
+
49
+ def has_children?
50
+ stage < hierarchy_length - 1
51
+ end
52
+
53
+ def available_values
54
+ dimension_class.available_child_values(hierarchy_name, ancestors).map(&:to_s)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,38 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ module Report #:nodoc:
3
+ # A report which is used to represent a tabular report.
4
+ class TableReport < ActiveRecord::Base
5
+ include AbstractReport
6
+ before_save :to_storage
7
+ after_save :from_storage
8
+ attr_accessor :format
9
+ attr_accessor :link_cell
10
+ attr_accessor :html_params
11
+
12
+ # Get any format options
13
+ def format
14
+ @format ||= {}
15
+ end
16
+
17
+ # Set to true if cells should be linked
18
+ def link_cell
19
+ @link_cell ||= false
20
+ end
21
+
22
+ # Hash of HTML parameters
23
+ def html_params
24
+ @html_params ||= {}
25
+ end
26
+
27
+ def view(params, options = {})
28
+ if options.has_key?(:sortable_with_totals)
29
+ options[:sortable] = true
30
+ options[:with_totals] = true
31
+ options.delete(:sortable_with_totals)
32
+ end
33
+
34
+ ActiveWarehouse::View::TableView.new(self, params, options)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 3
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'active_warehouse/view/report_helper'
2
+ require 'active_warehouse/view/abstract_view'
3
+ require 'active_warehouse/view/table_view'
4
+ require 'active_warehouse/view/sql_view'
5
+ require 'active_warehouse/view/crumb'
6
+ require 'active_warehouse/view/yui_adapter'
7
+ ActionView::Base.send :include, ReportHelper # TODO scope by ActiveWarehouse
8
+ ActionView::Base.send :include, ActiveWarehouse::View::YuiAdapter
9
+
@@ -0,0 +1,64 @@
1
+ class Crumb
2
+ def initialize(stage, dimension, params)
3
+ @params = params.dup
4
+ @stage = stage
5
+ @hierarchy = dimension.hierarchy
6
+ @name = dimension.ancestors[@stage-1] if @stage > 0
7
+ end
8
+
9
+ def link_to_params
10
+ invalid_params = @hierarchy[@stage..-1].map {|h| hierarchy_param(h) }.flatten
11
+ @params.delete_if { |k, v| invalid_params.include?(k) }
12
+ end
13
+
14
+ def link_to_name
15
+ @name || 'Top'
16
+ end
17
+ end
18
+
19
+ class RowCrumb < Crumb
20
+ def initialize(stage, dimension, params)
21
+ super
22
+ @params[:rstage] = @stage.to_s
23
+ end
24
+
25
+ #TODO - brittle, drive off of report's prefix param
26
+ def hierarchy_param(name)
27
+ "r_#{name}"
28
+ end
29
+
30
+ def crumb_type
31
+ "Row"
32
+ end
33
+
34
+ def self.gather(dimension, params)
35
+ crumbs = []
36
+ (0..dimension.stage).each do |stage|
37
+ crumbs << RowCrumb.new(stage, dimension, params)
38
+ end
39
+ crumbs
40
+ end
41
+ end
42
+
43
+ class ColumnCrumb < Crumb
44
+ def initialize(stage, dimension, params)
45
+ super
46
+ @params[:cstage] = @stage.to_s
47
+ end
48
+
49
+ def hierarchy_param(name)
50
+ "c_#{name}"
51
+ end
52
+
53
+ def crumb_type
54
+ "Column"
55
+ end
56
+
57
+ def self.gather(dimension, params)
58
+ crumbs = []
59
+ (0..dimension.stage).each do |stage|
60
+ crumbs << ColumnCrumb.new(stage, dimension, params)
61
+ end
62
+ crumbs
63
+ end
64
+ end
@@ -0,0 +1,98 @@
1
+ # Helper module for rendering reports.
2
+ module ReportHelper
3
+ # include ActiveWarehouse::Report::YuiAdapter
4
+
5
+ # UNSUPPORTED METHOD. Please use render_report_from instead.
6
+ # * <tt>report</tt>: The report instance
7
+ # * <tt>html_options</tt>: HTML options
8
+ def render_report(report, html_options={})
9
+ raise "Unsupported Render Method. Please use render_report_from instead."
10
+ end
11
+
12
+ def render_report_from(table_view, html_options = {})
13
+
14
+ # must use YUI if sorting is desired
15
+ if table_view.sortable?
16
+ raise "Sortable requires Yui4Rails plugin." unless defined? Yui4Rails
17
+ data_table = Yui4Rails::Widgets::DataTable.new("yui_table", yui_column_definitions(table_view), yui_data_rows(table_view),
18
+ table_view.with_totals? ? yui_totals_row(table_view) : "")
19
+
20
+ return data_table.render
21
+ end
22
+
23
+ # ... else render in html
24
+ report = table_view.report
25
+ column_dimension = table_view.column_dimension
26
+ row_dimension = table_view.row_dimension
27
+
28
+ table_attributes = {}
29
+ table_attributes[:class] = html_options[:report_class] ||= 'report'
30
+
31
+ # build the XHTML
32
+ x = ::Builder::XmlMarkup.new(:indent => 2)
33
+ x.table(table_attributes) do |x|
34
+
35
+ x.tr do |x| # column dimension
36
+ x.th
37
+ column_dimension.values.each do |col_dim_value|
38
+ x.th({:colspan => report.fact_attributes.length}) do |x|
39
+ x << link_to_if(column_dimension.has_children?, col_dim_value, table_view.column_link(col_dim_value))
40
+ end
41
+ end
42
+ end
43
+
44
+ x.tr do |x| # aggregated fact headers
45
+ # Generate the row dimension's header
46
+ x.th {|x| x << "#{row_dimension.hierarchy_level.to_s.humanize.titleize}"}
47
+ table_view.data_columns.each do |column|
48
+ x.th(column.label)
49
+ end
50
+ end
51
+
52
+ table_view.data_rows.each do |data_row|
53
+ x.tr do |x|
54
+ x.td do |x| # row dimension label
55
+ x << link_to_if(row_dimension.has_children?, data_row.dimension_value, table_view.row_link(data_row.dimension_value))
56
+ end
57
+
58
+ data_row.cells.each_with_index do |cell, index| # aggregated facts
59
+ x.td do |x|
60
+ x << link_to_if((report.link_cell && column_dimension.has_children? && row_dimension.has_children?), cell.value,
61
+ table_view.cell_link(cell.column_dimension_value,data_row.dimension_value)
62
+ )
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ if table_view.with_totals?
69
+ # Add summary row at the bottom of the report
70
+ x.tr({:class => 'total'}) do |x|
71
+ x.td { |x| x << "Grand Totals"}
72
+
73
+ table_view.data_columns.each_with_index do |column, index|
74
+ x.td do |x|
75
+ x << table_view.column_total(index)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def render_crumbs(crumbs)
84
+ breadcrumb_html = []
85
+ return if crumbs.size == 1
86
+
87
+ crumbs.each do |crumb|
88
+ if crumb != crumbs.last
89
+ breadcrumb_html << link_to(crumb.link_to_name, crumb.link_to_params)
90
+ else
91
+ breadcrumb_html << crumb.link_to_name
92
+ end
93
+ end
94
+ result = breadcrumb_html.join(' &#187; ')
95
+ result.blank? ? "" : "#{crumbs.last.crumb_type}: #{result}"
96
+ end
97
+
98
+ end
@@ -0,0 +1,134 @@
1
+ module ActiveWarehouse
2
+ module View
3
+
4
+ class TableView
5
+
6
+ include AbstractView
7
+
8
+ attr_reader :query_result, :fact_attributes,
9
+ :data_columns, :row_crumbs, :column_crumbs, :with_totals, :sortable
10
+
11
+ attr_accessor :ignore_columns
12
+
13
+ def initialize(report, params, options = {})
14
+ super
15
+ @with_totals = options.has_key?(:with_totals)
16
+ @sortable = options.has_key?(:sortable)
17
+ @ignore_columns = options.has_key?(:ignore_columns) ? options[:ignore_columns] : []
18
+ @column_crumbs = ColumnCrumb.gather(@column_dimension, @current_params)
19
+ @row_crumbs = RowCrumb.gather(@row_dimension, @current_params)
20
+ @query_result = execute_query
21
+
22
+ @fact_attributes = report.fact_attributes.map do |fact_attribute_name|
23
+ case fact_attribute_name
24
+ when Symbol, String
25
+ fact_attribute = report.fact_class.field_for_name(fact_attribute_name.to_s.dup)
26
+ raise "Field name #{fact_attribute_name} not defined in the fact #{report.fact_class}" if fact_attribute.nil?
27
+ end
28
+ fact_attribute
29
+ end
30
+ end
31
+
32
+ def execute_query
33
+ report.cube.query_row_and_column(row_dimension, column_dimension,
34
+ :conditions => report.conditions )
35
+ end
36
+
37
+ def column_total(column_index)
38
+ column = data_columns[column_index]
39
+ return "" unless column
40
+ column_attribute_key = column.fact_attribute.name.to_sym
41
+ return "" if ignore_columns.include?(column_attribute_key)
42
+ total = 0
43
+ data_rows.each do |row|
44
+ value = row.cells[column_index].raw_value
45
+ if value.is_a? Numeric
46
+ total += value
47
+ end
48
+ end
49
+
50
+ formatted_value = format_data(column_attribute_key,total)
51
+ # formatted_value = format_data(column.fact_attribute.name.to_sym,total)
52
+ end
53
+
54
+ def data_columns
55
+ @data_columns ||= []
56
+ return @data_columns unless @data_columns.empty?
57
+ if column_dimension
58
+ column_dimension.values.each do |dimension_value|
59
+ @fact_attributes.each do |fact_attribute|
60
+ @data_columns << ActiveWarehouse::Report::DataColumn.new(fact_attribute.label.humanize.titleize, dimension_value, fact_attribute)
61
+ end
62
+ end
63
+ end
64
+ @data_columns
65
+ end
66
+
67
+ def data_rows
68
+ @data_rows ||= []
69
+ return @data_rows unless @data_rows.empty?
70
+ if row_dimension
71
+ columns = self.data_columns
72
+ row_dimension.values.each do |row_dimension_value|
73
+ cells = []
74
+ columns.each do |column|
75
+ cells << data_cell(column.fact_attribute, column.dimension_value, row_dimension_value)
76
+ end
77
+ @data_rows << ActiveWarehouse::Report::DataRow.new(row_dimension_value, cells)
78
+ end
79
+ end
80
+ @data_rows
81
+ end
82
+
83
+ def data_cell(fact_attribute, column_dimension_value, row_dimension_value)
84
+ value = ''
85
+ raw_value = nil
86
+ case fact_attribute
87
+ when ActiveWarehouse::AggregateField
88
+ raw_value = query_result.value(row_dimension_value, column_dimension_value, fact_attribute.label)
89
+ when ActiveWarehouse::CalculatedField
90
+ raw_value = fact_attribute.calculate(query_result.values(row_dimension_value, column_dimension_value))
91
+ end
92
+
93
+ formatted_value = format_data(fact_attribute.name.to_sym,raw_value)
94
+ ActiveWarehouse::Report::DataCell.new(column_dimension_value, row_dimension_value, fact_attribute, raw_value, formatted_value)
95
+ end
96
+
97
+ def column_link(column_dimension_value)
98
+ current_params.merge({:cstage => column_dimension.stage + 1,
99
+ "#{column_dimension.param_prefix}_#{column_dimension.hierarchy_level}" => column_dimension_value})
100
+ end
101
+
102
+ def row_link(row_dimension_value)
103
+ current_params.merge({:rstage => row_dimension.stage + 1,
104
+ "#{row_dimension.param_prefix}_#{row_dimension.hierarchy_level}" => row_dimension_value})
105
+ end
106
+
107
+ def cell_link(column_dimension_value,row_dimension_value)
108
+ current_params.merge({:rstage => row_dimension.stage + 1, :cstage => column_dimension.stage + 1,
109
+ "#{column_dimension.param_prefix}_#{column_dimension.hierarchy_level}" => column_dimension_value,
110
+ "#{row_dimension.param_prefix}_#{row_dimension.hierarchy_level}" => row_dimension_value})
111
+ end
112
+
113
+ def sortable?
114
+ @sortable
115
+ end
116
+
117
+ def with_totals?
118
+ @with_totals
119
+ end
120
+
121
+ def format_data(field, raw_value)
122
+ format = report.format[field]
123
+ if format && format.is_a?(Proc)
124
+ value = format.call(raw_value)
125
+ elsif format == :currency
126
+ value = sprintf("$%.2f", raw_value)
127
+ else
128
+ value = raw_value.to_s
129
+ end
130
+ end
131
+ end
132
+
133
+ end
134
+ end