activewarehouse 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +41 -0
- data/Rakefile +121 -0
- data/TODO +4 -0
- data/db/migrations/001_create_table_reports.rb +28 -0
- data/doc/agg_queries.txt +26 -0
- data/doc/agg_queries_results.txt +150 -0
- data/doc/queries.txt +35 -0
- data/generators/cube/USAGE +1 -0
- data/generators/cube/cube_generator.rb +28 -0
- data/generators/cube/templates/model.rb +3 -0
- data/generators/cube/templates/unit_test.rb +8 -0
- data/generators/dimension/USAGE +1 -0
- data/generators/dimension/dimension_generator.rb +46 -0
- data/generators/dimension/templates/fixture.yml +5 -0
- data/generators/dimension/templates/migration.rb +20 -0
- data/generators/dimension/templates/model.rb +3 -0
- data/generators/dimension/templates/unit_test.rb +10 -0
- data/generators/fact/USAGE +1 -0
- data/generators/fact/fact_generator.rb +46 -0
- data/generators/fact/templates/fixture.yml +5 -0
- data/generators/fact/templates/migration.rb +11 -0
- data/generators/fact/templates/model.rb +3 -0
- data/generators/fact/templates/unit_test.rb +10 -0
- data/install.rb +5 -0
- data/lib/active_warehouse.rb +65 -0
- data/lib/active_warehouse/builder.rb +2 -0
- data/lib/active_warehouse/builder/date_dimension_builder.rb +65 -0
- data/lib/active_warehouse/builder/random_data_builder.rb +13 -0
- data/lib/active_warehouse/core_ext.rb +1 -0
- data/lib/active_warehouse/core_ext/time.rb +5 -0
- data/lib/active_warehouse/core_ext/time/calculations.rb +40 -0
- data/lib/active_warehouse/migrations.rb +65 -0
- data/lib/active_warehouse/model.rb +5 -0
- data/lib/active_warehouse/model/aggregate.rb +244 -0
- data/lib/active_warehouse/model/cube.rb +273 -0
- data/lib/active_warehouse/model/dimension.rb +3 -0
- data/lib/active_warehouse/model/dimension/bridge.rb +32 -0
- data/lib/active_warehouse/model/dimension/dimension.rb +152 -0
- data/lib/active_warehouse/model/dimension/hierarchical_dimension.rb +35 -0
- data/lib/active_warehouse/model/fact.rb +96 -0
- data/lib/active_warehouse/model/report.rb +3 -0
- data/lib/active_warehouse/model/report/abstract_report.rb +121 -0
- data/lib/active_warehouse/model/report/chart_report.rb +9 -0
- data/lib/active_warehouse/model/report/table_report.rb +23 -0
- data/lib/active_warehouse/version.rb +9 -0
- data/lib/active_warehouse/view.rb +2 -0
- data/lib/active_warehouse/view/report_helper.rb +213 -0
- data/tasks/active_warehouse_tasks.rake +50 -0
- metadata +144 -0
@@ -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,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 @@
|
|
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
|
data/install.rb
ADDED
@@ -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,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 @@
|
|
1
|
+
require 'active_warehouse/core_ext/time'
|
@@ -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
|