activewarehouse 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/README +41 -0
  2. data/Rakefile +121 -0
  3. data/TODO +4 -0
  4. data/db/migrations/001_create_table_reports.rb +28 -0
  5. data/doc/agg_queries.txt +26 -0
  6. data/doc/agg_queries_results.txt +150 -0
  7. data/doc/queries.txt +35 -0
  8. data/generators/cube/USAGE +1 -0
  9. data/generators/cube/cube_generator.rb +28 -0
  10. data/generators/cube/templates/model.rb +3 -0
  11. data/generators/cube/templates/unit_test.rb +8 -0
  12. data/generators/dimension/USAGE +1 -0
  13. data/generators/dimension/dimension_generator.rb +46 -0
  14. data/generators/dimension/templates/fixture.yml +5 -0
  15. data/generators/dimension/templates/migration.rb +20 -0
  16. data/generators/dimension/templates/model.rb +3 -0
  17. data/generators/dimension/templates/unit_test.rb +10 -0
  18. data/generators/fact/USAGE +1 -0
  19. data/generators/fact/fact_generator.rb +46 -0
  20. data/generators/fact/templates/fixture.yml +5 -0
  21. data/generators/fact/templates/migration.rb +11 -0
  22. data/generators/fact/templates/model.rb +3 -0
  23. data/generators/fact/templates/unit_test.rb +10 -0
  24. data/install.rb +5 -0
  25. data/lib/active_warehouse.rb +65 -0
  26. data/lib/active_warehouse/builder.rb +2 -0
  27. data/lib/active_warehouse/builder/date_dimension_builder.rb +65 -0
  28. data/lib/active_warehouse/builder/random_data_builder.rb +13 -0
  29. data/lib/active_warehouse/core_ext.rb +1 -0
  30. data/lib/active_warehouse/core_ext/time.rb +5 -0
  31. data/lib/active_warehouse/core_ext/time/calculations.rb +40 -0
  32. data/lib/active_warehouse/migrations.rb +65 -0
  33. data/lib/active_warehouse/model.rb +5 -0
  34. data/lib/active_warehouse/model/aggregate.rb +244 -0
  35. data/lib/active_warehouse/model/cube.rb +273 -0
  36. data/lib/active_warehouse/model/dimension.rb +3 -0
  37. data/lib/active_warehouse/model/dimension/bridge.rb +32 -0
  38. data/lib/active_warehouse/model/dimension/dimension.rb +152 -0
  39. data/lib/active_warehouse/model/dimension/hierarchical_dimension.rb +35 -0
  40. data/lib/active_warehouse/model/fact.rb +96 -0
  41. data/lib/active_warehouse/model/report.rb +3 -0
  42. data/lib/active_warehouse/model/report/abstract_report.rb +121 -0
  43. data/lib/active_warehouse/model/report/chart_report.rb +9 -0
  44. data/lib/active_warehouse/model/report/table_report.rb +23 -0
  45. data/lib/active_warehouse/version.rb +9 -0
  46. data/lib/active_warehouse/view.rb +2 -0
  47. data/lib/active_warehouse/view/report_helper.rb +213 -0
  48. data/tasks/active_warehouse_tasks.rake +50 -0
  49. metadata +144 -0
@@ -0,0 +1,3 @@
1
+ class <%= class_name %> < ActiveWarehouse::Cube
2
+
3
+ end
@@ -0,0 +1,8 @@
1
+ require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper'
2
+
3
+ class <%= class_name %>Test < Test::Unit::TestCase
4
+ # Replace this with your real tests.
5
+ def test_truth
6
+ assert true
7
+ end
8
+ end
@@ -0,0 +1 @@
1
+ ./script/generate dimension NAME
@@ -0,0 +1,46 @@
1
+ class DimensionGenerator < Rails::Generator::NamedBase
2
+ attr_accessor :file_name
3
+
4
+ default_options :skip_migration => false
5
+
6
+ def initialize(runtime_args, runtime_options = {})
7
+ super
8
+
9
+ @name = @name.tableize.singularize
10
+ @table_name = "#{@name}_dimension"
11
+ @class_name = "#{@name.camelize}Dimension"
12
+ @file_name = "#{@class_name.tableize.singularize}"
13
+ end
14
+
15
+ def manifest
16
+ record do |m|
17
+ # Check for class naming collisions.
18
+ m.class_collisions class_path, "#{class_name}", "#{class_name}Test"
19
+
20
+ # Create required directories if necessary
21
+ m.directory File.join('app/models', class_path)
22
+ m.directory File.join('test/unit', class_path)
23
+ m.directory File.join('test/fixtures', class_path)
24
+
25
+ # Generate the files
26
+ m.template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
27
+ m.template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb")
28
+ m.template 'fixture.yml', File.join('test/fixtures', class_path, "#{table_name}.yml")
29
+
30
+ # Generate the migration unless :skip_migration option is specified
31
+ unless options[:skip_migration]
32
+ m.migration_template 'migration.rb', 'db/migrate', :assigns => {
33
+ :migration_name => "Create#{class_name.gsub(/::/, '')}"
34
+ }, :migration_file_name => "create_#{file_name.gsub(/\//, '_')}"
35
+ end
36
+ end
37
+ end
38
+
39
+ protected
40
+ def add_options!(opt)
41
+ opt.separator ''
42
+ opt.separator 'Options:'
43
+ opt.on("--skip-migration",
44
+ "Don't generate a migration file for this dimension") { |v| options[:skip_migration] = v }
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
+ first:
3
+ id: 1
4
+ another:
5
+ id: 2
@@ -0,0 +1,20 @@
1
+ class <%= migration_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ fields = {
4
+ # Add dimension attributes here as name => type
5
+ # Example: :store_name => :string
6
+ }
7
+ create_table :<%= table_name %> do |t|
8
+ fields.each do |name,type|
9
+ t.column name, type
10
+ end
11
+ end
12
+ fields.each do |name,type|
13
+ add_index :<%= table_name %>, name unless type == :text
14
+ end
15
+ end
16
+
17
+ def self.down
18
+ drop_table :<%= table_name %>
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ class <%= class_name %> < ActiveWarehouse::Dimension
2
+
3
+ end
@@ -0,0 +1,10 @@
1
+ require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper'
2
+
3
+ class <%= class_name %>Test < Test::Unit::TestCase
4
+ fixtures :<%= table_name %>
5
+
6
+ # Replace this with your real tests.
7
+ def test_truth
8
+ assert true
9
+ end
10
+ end
@@ -0,0 +1 @@
1
+ ./script/generate fact NAME
@@ -0,0 +1,46 @@
1
+ class FactGenerator < Rails::Generator::NamedBase
2
+ attr_accessor :file_name
3
+
4
+ default_options :skip_migration => false
5
+
6
+ def initialize(runtime_args, runtime_options = {})
7
+ super
8
+
9
+ @name = @name.underscore
10
+ @table_name = "#{@name}_facts"
11
+ @class_name = "#{@name.camelize}Fact"
12
+ @file_name = "#{@class_name.tableize.singularize}"
13
+ end
14
+
15
+ def manifest
16
+ record do |m|
17
+ # Check for class naming collisions.
18
+ m.class_collisions class_path, "#{class_name}", "#{class_name}Test"
19
+
20
+ # Create required directories if necessary
21
+ m.directory File.join('app/models', class_path)
22
+ m.directory File.join('test/unit', class_path)
23
+ m.directory File.join('test/fixtures', class_path)
24
+
25
+ # Generate the files
26
+ m.template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
27
+ m.template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb")
28
+ m.template 'fixture.yml', File.join('test/fixtures', class_path, "#{table_name}.yml")
29
+
30
+ # Generate the migration unless :skip_migration option is specified
31
+ unless options[:skip_migration]
32
+ m.migration_template 'migration.rb', 'db/migrate', :assigns => {
33
+ :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"
34
+ }, :migration_file_name => "create_#{file_name.gsub(/\//, '_').pluralize}"
35
+ end
36
+ end
37
+ end
38
+
39
+ protected
40
+ def add_options!(opt)
41
+ opt.separator ''
42
+ opt.separator 'Options:'
43
+ opt.on("--skip-migration",
44
+ "Don't generate a migration file for this fact") { |v| options[:skip_migration] = v }
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ # Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
+ first_fact:
3
+ id: 1
4
+ second_fact:
5
+ id: 2
@@ -0,0 +1,11 @@
1
+ class <%= migration_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :<%= table_name %> do |t|
4
+
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ drop_table :<%= table_name %>
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ class <%= class_name %> < ActiveWarehouse::Fact
2
+
3
+ end
@@ -0,0 +1,10 @@
1
+ require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper'
2
+
3
+ class <%= class_name %>Test < Test::Unit::TestCase
4
+ fixtures :<%= table_name %>
5
+
6
+ # Replace this with your real tests.
7
+ def test_truth
8
+ assert true
9
+ end
10
+ end
data/install.rb ADDED
@@ -0,0 +1,5 @@
1
+ # migrate
2
+ #puts "Migrating ActiveWarehouse"
3
+ #migration_directory = File.join(File.dirname(__FILE__), 'db', 'migrations')
4
+ #ActiveRecord::Migrator.migrate(migration_directory, nil)
5
+ # puts IO.read(File.join(File.dirname(__FILE__), 'README'))
@@ -0,0 +1,65 @@
1
+ #--
2
+ # Copyright (c) 2006 Anthony Eden
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ $:.unshift(File.dirname(__FILE__)) unless
25
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
26
+
27
+ unless defined?(ActiveSupport)
28
+ begin
29
+ $:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
30
+ require 'active_support'
31
+ rescue LoadError
32
+ require 'rubygems'
33
+ require_gem 'activesupport'
34
+ end
35
+ end
36
+
37
+ unless defined?(ActiveRecord)
38
+ begin
39
+ $:.unshift(File.dirname(__FILE__) + "/../../activerecord/lib")
40
+ require 'active_record'
41
+ rescue LoadError
42
+ require 'rubygems'
43
+ require_gem 'activerecord'
44
+ end
45
+ end
46
+
47
+ unless defined?(ActionView)
48
+ begin
49
+ $:.unshift(File.dirname(__FILE__) + "/../../actionpack/lib")
50
+ require 'action_pack'
51
+ require 'action_controller'
52
+ require 'action_view'
53
+ rescue LoadError
54
+ require 'rubygems'
55
+ require_gem 'actionpack'
56
+ end
57
+ end
58
+
59
+
60
+ require 'active_warehouse/version'
61
+ require 'active_warehouse/core_ext'
62
+ require 'active_warehouse/model'
63
+ require 'active_warehouse/view'
64
+ require 'active_warehouse/builder'
65
+ require 'active_warehouse/migrations'
@@ -0,0 +1,2 @@
1
+ require 'active_warehouse/builder/date_dimension_builder'
2
+ require 'active_warehouse/builder/random_data_builder'
@@ -0,0 +1,65 @@
1
+ module ActiveWarehouse
2
+ module Builder
3
+ class DateDimensionBuilder
4
+ attr_accessor :start_date, :end_date, :holiday_indicators
5
+ cattr_accessor :weekday_indicators
6
+ @@weekday_indicators = ['Weekend','Weekday','Weekday','Weekday','Weekday','Weekday','Weekend']
7
+
8
+ def initialize(start_date=Time.now.years_ago(5), end_date=Time.now)
9
+ @start_date = start_date
10
+ @end_date = end_date
11
+ @holiday_indicators = []
12
+ end
13
+
14
+ # Returns an array of hashes representing records in the dimension
15
+ # The values for each record are accessed by name
16
+ def build(options={})
17
+ records = []
18
+ date = start_date
19
+ while date <= end_date
20
+ record = {}
21
+ record[:date] = date.strftime("%m/%d/%Y")
22
+ record[:full_date_description] = date.strftime("%B %d,%Y")
23
+ record[:day_of_week] = date.strftime("%A")
24
+ #record[:day_number_in_epoch] = date.to_i / 24
25
+ #record[:week_number_in_epoch] = date.to_i / (24 * 7)
26
+ #record[:month_number_in_epoch] = date.to_i / (24 * 7 * 30)
27
+ record[:day_number_in_calendar_month] = date.day
28
+ record[:day_number_in_calendar_year] = date.yday
29
+ record[:day_number_in_fiscal_month] = date.day # should this be different from CY?
30
+ record[:day_number_in_fiscal_year] = date.fiscal_year_yday
31
+ #record[:last_day_in_week_indicator] =
32
+ #record[:last_day_in_month_indicator] =
33
+ #record[:calendar_week_ending_date] =
34
+ record[:calendar_week] = "Week #{date.week}"
35
+ record[:calendar_week_number_in_year] = date.week
36
+ record[:calendar_month_name] = date.strftime("%B")
37
+ record[:calendar_month_number_in_year] = date.month
38
+ record[:calendar_year_month] = date.strftime("%Y-%m")
39
+ record[:calendar_quarter] = "Q#{date.quarter}"
40
+ record[:calendar_year_quarter] = "#{date.strftime('%Y')}-#{record[:calendar_quarter]}"
41
+ #record[:calendar_half_year] =
42
+ record[:calendar_year] = "#{date.year}"
43
+ record[:fiscal_week] = "FY Week #{date.fiscal_year_week}"
44
+ record[:fiscal_week_number_in_year] = date.fiscal_year_week
45
+ record[:fiscal_month] = date.fiscal_year_month
46
+ record[:fiscal_month_number_in_year] = date.fiscal_year_month
47
+ record[:fiscal_year_month] = "FY#{date.fiscal_year}-" + date.fiscal_year_month.to_s.rjust(2, '0')
48
+ record[:fiscal_quarter] = "FY Q#{date.fiscal_year_quarter}"
49
+ record[:fiscal_year_quarter] = "FY#{date.fiscal_year}-Q#{date.fiscal_year_quarter}"
50
+ #record[:fiscal_half_year] =
51
+ record[:fiscal_year] = "FY#{date.fiscal_year}"
52
+ record[:holiday_indicator] = holiday_indicators.include?(date) ? 'Holiday' : 'Nonholiday'
53
+ record[:weekday_indicator] = weekday_indicators[date.wday]
54
+ record[:selling_season] = 'None'
55
+ record[:major_event] = 'None'
56
+ record[:sql_date_stamp] = date
57
+
58
+ records << record
59
+ date = date.tomorrow
60
+ end
61
+ records
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveWarehouse
2
+ module Builder
3
+ class RandomDataBuilder
4
+ def initialize
5
+
6
+ end
7
+
8
+ def build(options={})
9
+
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1 @@
1
+ require 'active_warehouse/core_ext/time'
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/time/calculations'
2
+
3
+ class Time#:nodoc:
4
+ include ActiveWarehouse::CoreExtensions::Time::Calculations
5
+ end
@@ -0,0 +1,40 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ module CoreExtensions #:nodoc:
3
+ module Time #:nodoc:
4
+ # Enables the use of time calculations within Time itself
5
+ module Calculations
6
+ def week
7
+ cyw = ((yday - 1) / 7) + 1
8
+ cyw = 52 if cyw == 53
9
+ cyw
10
+ end
11
+ def quarter
12
+ ((month - 1) / 3) + 1
13
+ end
14
+ def fiscal_year_week(offset_month=10)
15
+ fyw = ((fiscal_year_yday(offset_month) - 1) / 7) + 1
16
+ fyw = 52 if fyw == 53
17
+ fyw
18
+ end
19
+ def fiscal_year_month(offset_month=10)
20
+ shifted_month = month - (offset_month - 1)
21
+ shifted_month += 12 if shifted_month < 0
22
+ shifted_month
23
+ end
24
+ def fiscal_year_quarter(offset_month=10)
25
+ ((fiscal_year_month(offset_month) - 1) / 3) + 1
26
+ end
27
+ def fiscal_year(offset_month=10)
28
+ month >= offset_month ? year + 1 : year
29
+ end
30
+ def fiscal_year_yday(offset_month=10)
31
+ offset_days = 0
32
+ 1.upto(offset_month - 1) { |m| offset_days += ::Time.days_in_month(m, year) }
33
+ shifted_year_day = yday - offset_days
34
+ shifted_year_day += 365 if shifted_year_day <= 0
35
+ shifted_year_day
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,65 @@
1
+ module ActiveWarehouse #:nodoc:
2
+ # Responsible for migrating ActiveWarehouse
3
+ class Migrator < ActiveRecord::Migrator
4
+
5
+ class << self
6
+ def schema_info_table_name #:nodoc:
7
+ ActiveRecord::Base.table_name_prefix + 'activewarehouse_schema_info' + ActiveRecord::Base.table_name_suffix
8
+ end
9
+
10
+ def current_version #:nodoc:
11
+ result = ActiveRecord::Base.connection.select_one("SELECT version FROM #{schema_info_table_name}")
12
+ if result
13
+ result['version'].to_i
14
+ else
15
+ # There probably isn't an entry for this plugin in the migration info table.
16
+ # We need to create that entry, and set the version to 0
17
+ ActiveRecord::Base.connection.execute("INSERT INTO #{schema_info_table_name} (version) VALUES (0)")
18
+ 0
19
+ end
20
+ end
21
+ end
22
+
23
+ def set_schema_version(version)
24
+ ActiveRecord::Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{down? ? version.to_i - 1 : version.to_i}")
25
+ end
26
+ end
27
+ end
28
+
29
+ module ActiveRecord #:nodoc:
30
+ module ConnectionAdapters #:nodoc:
31
+ module SchemaStatements #:nodoc:
32
+ def initialize_schema_information_with_activewarehouse
33
+ initialize_schema_information_without_activewarehouse
34
+
35
+ begin
36
+ execute "CREATE TABLE #{ActiveWarehouse::Migrator.schema_info_table_name} (version #{type_to_sql(:integer)})"
37
+ rescue ActiveRecord::StatementInvalid
38
+ # Schema has been initialized
39
+ end
40
+ end
41
+ alias_method_chain :initialize_schema_information, :activewarehouse
42
+
43
+ def dump_schema_information_with_activewarehouse
44
+ schema_information = []
45
+
46
+ dump = dump_schema_information_without_activewarehouse
47
+ schema_information << dump if dump
48
+
49
+ begin
50
+ plugins = ActiveRecord::Base.connection.select_all("SELECT * FROM #{ActiveWarehouse::Migrator.schema_info_table_name}")
51
+ plugins.each do |plugin|
52
+ if (version = plugin['version'].to_i) > 0
53
+ schema_information << "INSERT INTO #{ActiveWarehouse::Migrator.schema_info_table_name} (version) VALUES (#{version})"
54
+ end
55
+ end
56
+ rescue ActiveRecord::StatementInvalid
57
+ # No Schema Info
58
+ end
59
+
60
+ schema_information.join("\n")
61
+ end
62
+ alias_method_chain :dump_schema_information, :activewarehouse
63
+ end
64
+ end
65
+ end