activewarehouse 0.1.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 (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