activewarehouse 0.1.0 → 0.2.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.
- data/README +62 -17
- data/Rakefile +17 -0
- data/generators/bridge/USAGE +1 -0
- data/generators/bridge/bridge_generator.rb +46 -0
- data/generators/bridge/templates/fixture.yml +5 -0
- data/generators/bridge/templates/migration.rb +20 -0
- data/generators/bridge/templates/model.rb +3 -0
- data/generators/dimension/templates/unit_test.rb +0 -2
- data/generators/dimension_view/USAGE +1 -0
- data/generators/dimension_view/dimension_view_generator.rb +62 -0
- data/generators/dimension_view/templates/migration.rb +11 -0
- data/generators/dimension_view/templates/model.rb +3 -0
- data/generators/dimension_view/templates/unit_test.rb +10 -0
- data/init.rb +1 -0
- data/lib/active_warehouse.rb +24 -9
- data/lib/active_warehouse/{model/aggregate.rb → aggregate.rb} +29 -13
- data/lib/active_warehouse/builder/date_dimension_builder.rb +21 -6
- data/lib/active_warehouse/builder/random_data_builder.rb +204 -3
- data/lib/active_warehouse/compat/compat.rb +49 -0
- data/lib/active_warehouse/{model/cube.rb → cube.rb} +47 -17
- data/lib/active_warehouse/dimension.rb +296 -0
- data/lib/active_warehouse/dimension/bridge.rb +15 -0
- data/lib/active_warehouse/dimension/dimension_view.rb +11 -0
- data/lib/active_warehouse/dimension/hierarchical_dimension.rb +60 -0
- data/lib/active_warehouse/dimension/slowly_changing_dimension.rb +137 -0
- data/lib/active_warehouse/{model/fact.rb → fact.rb} +45 -10
- data/lib/active_warehouse/migrations.rb +1 -2
- data/lib/active_warehouse/report.rb +3 -0
- data/lib/active_warehouse/{model/report → report}/abstract_report.rb +0 -0
- data/lib/active_warehouse/{model/report → report}/chart_report.rb +0 -0
- data/lib/active_warehouse/{model/report → report}/table_report.rb +0 -0
- data/lib/active_warehouse/version.rb +2 -2
- data/lib/active_warehouse/view/report_helper.rb +2 -1
- data/tasks/active_warehouse_tasks.rake +54 -0
- metadata +43 -21
- data/doc/agg_queries.txt +0 -26
- data/doc/agg_queries_results.txt +0 -150
- data/doc/queries.txt +0 -35
- data/lib/active_warehouse/model.rb +0 -5
- data/lib/active_warehouse/model/dimension.rb +0 -3
- data/lib/active_warehouse/model/dimension/bridge.rb +0 -32
- data/lib/active_warehouse/model/dimension/dimension.rb +0 -152
- data/lib/active_warehouse/model/dimension/hierarchical_dimension.rb +0 -35
- data/lib/active_warehouse/model/report.rb +0 -3
data/README
CHANGED
@@ -1,32 +1,58 @@
|
|
1
|
-
ActiveWarehouse
|
2
|
-
|
3
|
-
The ActiveWarehouse library provides classes and functions which help with building Data Warehouses using Rails. It can be installed either as a plugin or as a Gem.
|
1
|
+
== ActiveWarehouse
|
2
|
+
|
3
|
+
The ActiveWarehouse library provides classes and functions which help with building Data Warehouses using Rails. It can be installed either as a plugin or as a Gem.
|
4
|
+
|
5
|
+
To install as a plugin just use:
|
6
|
+
|
7
|
+
script/plugin install --force svn://rubyforge.org/var/svn/activewarehouse/activewarehouse/trunk
|
8
|
+
|
9
|
+
To get the latest edge version.
|
10
|
+
|
11
|
+
To install as a Gem, use:
|
12
|
+
|
13
|
+
gem install activewarehouse
|
14
|
+
|
15
|
+
At this point you can use ActiveWarehouse in any application using:
|
4
16
|
|
5
17
|
require_gem 'activewarehouse'
|
6
18
|
require 'active_warehouse'
|
7
19
|
|
8
|
-
|
20
|
+
If you want to use the Gem and link to your Rails app then use the gemsonrails project:
|
21
|
+
|
22
|
+
gem install gemsonrails
|
23
|
+
|
24
|
+
And then in your Rails app:
|
9
25
|
|
10
|
-
|
26
|
+
rake gems:link GEM=activewarehouse
|
27
|
+
|
28
|
+
== Generators
|
11
29
|
|
12
|
-
Generators
|
13
|
-
===============
|
14
30
|
ActiveWarehouse comes with several generators
|
15
31
|
|
16
|
-
|
17
|
-
|
32
|
+
script/generate fact Sales
|
33
|
+
script/generate fact sales
|
18
34
|
|
19
|
-
|
35
|
+
Creates a SalesFact class and a sales_facts table.
|
20
36
|
|
21
|
-
|
22
|
-
|
37
|
+
script/generate dimension Region
|
38
|
+
script/generate dimension region
|
23
39
|
|
24
|
-
|
40
|
+
Creates a RegionDimension class and a region_dimension table.
|
25
41
|
|
26
|
-
|
27
|
-
|
42
|
+
script/generate cube RegionalSales
|
43
|
+
script/generate cube regional_sales
|
28
44
|
|
29
|
-
|
45
|
+
Creates a RegionalSalesCube class.
|
46
|
+
|
47
|
+
script/generate bridge CustomerHierarchy
|
48
|
+
script/generate bridge customer_hierarchy
|
49
|
+
|
50
|
+
Creates a CustomerHierarchyBridge class.
|
51
|
+
|
52
|
+
script/generate dimension_view OrderDate Date
|
53
|
+
script/generate dimension_view order_date date
|
54
|
+
|
55
|
+
Creates an OrderDateDimension class which is represented by a view on top of the DateDimension.
|
30
56
|
|
31
57
|
The rules for naming are as follows:
|
32
58
|
|
@@ -38,4 +64,23 @@ Dimensions:
|
|
38
64
|
Both the class name and the table name are suffixed by "_dimension".
|
39
65
|
Cube:
|
40
66
|
Cube class is singular. If a cube table is created it will also be singular.
|
41
|
-
|
67
|
+
Bridge:
|
68
|
+
Bridge classes and tables are both singular.
|
69
|
+
Both the class name and the table name are suffixed by "_bridge".
|
70
|
+
Dimension View:
|
71
|
+
Dimension View classes are singular. The underlying data structure is a view on top of an existing dimension.
|
72
|
+
Both the class name and the view name are suffixed by "_dimension"
|
73
|
+
|
74
|
+
== ETL
|
75
|
+
|
76
|
+
The ActiveWarehouse plugin does not directly handle Extract-Transform-Load processes, however the ActiveWarehouse ETL gem (installed separately) can help. To install it use:
|
77
|
+
|
78
|
+
gem install activewarehouse-etl
|
79
|
+
|
80
|
+
More information on the ETL process can be found at http://activewarehouse.rubyforge.org/etl
|
81
|
+
|
82
|
+
== Tutorial
|
83
|
+
|
84
|
+
A tutorial for ActiveWarehouse is available online at http://anthonyeden.com/2006/12/20/activewarehouse-example-with-rails-svn-logs
|
85
|
+
|
86
|
+
You can also get a demo from the ActiveWarehouse subversion repository. Look in the SVN_ROOT/demo directory.
|
data/Rakefile
CHANGED
@@ -27,6 +27,17 @@ Rake::TestTask.new(:test) do |t|
|
|
27
27
|
t.verbose = true
|
28
28
|
end
|
29
29
|
|
30
|
+
namespace :test do
|
31
|
+
desc 'Measures test coverage'
|
32
|
+
task :coverage do
|
33
|
+
rm_f "coverage"
|
34
|
+
rm_f "coverage.data"
|
35
|
+
rcov = "rcov --aggregate coverage.data --text-summary -Ilib"
|
36
|
+
system("#{rcov} test/**/*_test.rb")
|
37
|
+
system("open coverage/index.html") if PLATFORM['darwin']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
30
41
|
desc 'Generate documentation for the active_warehouse plugin.'
|
31
42
|
Rake::RDocTask.new(:rdoc) do |rdoc|
|
32
43
|
rdoc.rdoc_dir = 'rdoc'
|
@@ -61,6 +72,7 @@ spec = Gem::Specification.new do |s|
|
|
61
72
|
s.add_dependency('activesupport', '>= 1.3.1.5618')
|
62
73
|
s.add_dependency('activerecord', '>= 1.14.4.5618')
|
63
74
|
s.add_dependency('actionpack', '>= 1.12.5.5618')
|
75
|
+
s.add_dependency('rails_sql_views', '>= 0.1.0')
|
64
76
|
|
65
77
|
s.rdoc_options << '--exclude' << '.'
|
66
78
|
s.has_rdoc = false
|
@@ -118,4 +130,9 @@ task :release => [ :package ] do
|
|
118
130
|
puts release_command
|
119
131
|
system(release_command)
|
120
132
|
end
|
133
|
+
end
|
134
|
+
|
135
|
+
desc "Publish the API documentation"
|
136
|
+
task :pdoc => [:rdoc] do
|
137
|
+
Rake::SshDirPublisher.new("aeden@rubyforge.org", "/var/www/gforge-projects/activewarehouse/rdoc", "rdoc").upload
|
121
138
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
./script/generate bridge NAME
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class BridgeGenerator < 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}_bridge"
|
11
|
+
@class_name = "#{@name.camelize}Bridge"
|
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 bridge") { |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 bridge attributes here as name => type
|
5
|
+
# Example: :top_flag => :integer
|
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 dimension_view NAME
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class DimensionViewGenerator < Rails::Generator::NamedBase
|
2
|
+
attr_accessor :file_name, :view_name, :query_target_name, :query_target_table_name, :view_query, :view_attributes
|
3
|
+
|
4
|
+
default_options :skip_migration => false
|
5
|
+
|
6
|
+
def initialize(runtime_args, runtime_options = {})
|
7
|
+
super
|
8
|
+
|
9
|
+
usage if runtime_args.length != 2
|
10
|
+
|
11
|
+
@name = @name.tableize.singularize
|
12
|
+
@query_target_name = runtime_args[1]
|
13
|
+
# define the view and query target table name
|
14
|
+
@view_name = "#{@name}_dimension"
|
15
|
+
@query_target_table_name = "#{query_target_name}_dimension"
|
16
|
+
# define the view class name and query target class name
|
17
|
+
@class_name = "#{view_name.camelize}"
|
18
|
+
@query_target_class_name = "#{query_target_table_name.camelize}"
|
19
|
+
# define the output file name
|
20
|
+
@file_name = "#{class_name.tableize.singularize}"
|
21
|
+
# define the query target class and expose its columns as attributes for the view
|
22
|
+
@query_target_class = @query_target_class_name.constantize
|
23
|
+
@view_attributes = @query_target_class.column_names
|
24
|
+
@view_query = "select * from #{query_target_table_name}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def manifest
|
28
|
+
record do |m|
|
29
|
+
# Check for class naming collisions.
|
30
|
+
m.class_collisions class_path, "#{class_name}", "#{class_name}Test"
|
31
|
+
|
32
|
+
# Create required directories if necessary
|
33
|
+
m.directory File.join('app/models', class_path)
|
34
|
+
m.directory File.join('test/unit', class_path)
|
35
|
+
m.directory File.join('test/fixtures', class_path)
|
36
|
+
|
37
|
+
# Generate the files
|
38
|
+
m.template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
|
39
|
+
m.template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb")
|
40
|
+
m.template 'fixture.yml', File.join('test/fixtures', class_path, "#{table_name}.yml")
|
41
|
+
|
42
|
+
# Generate the migration unless :skip_migration option is specified
|
43
|
+
unless options[:skip_migration]
|
44
|
+
m.migration_template 'migration.rb', 'db/migrate', :assigns => {
|
45
|
+
:migration_name => "Create#{class_name.gsub(/::/, '')}"
|
46
|
+
}, :migration_file_name => "create_#{file_name.gsub(/\//, '_')}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def banner
|
52
|
+
"Usage: #{$0} #{spec.name} #{spec.name.camelize}Name SelectTarget [options]"
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
def add_options!(opt)
|
57
|
+
opt.separator ''
|
58
|
+
opt.separator 'Options:'
|
59
|
+
opt.on("--skip-migration",
|
60
|
+
"Don't generate a migration file for this dimension view") { |v| options[:skip_migration] = v }
|
61
|
+
end
|
62
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'active_warehouse'
|
data/lib/active_warehouse.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
# This source file requires all of the necessary gems and source files for ActiveWarehouse. If you
|
2
|
+
# load this source file all of the other required files and gems will also be brought into the
|
3
|
+
# runtime.
|
4
|
+
|
1
5
|
#--
|
2
|
-
# Copyright (c) 2006 Anthony Eden
|
6
|
+
# Copyright (c) 2006-2007 Anthony Eden
|
3
7
|
#
|
4
8
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
9
|
# a copy of this software and associated documentation files (the
|
@@ -24,13 +28,17 @@
|
|
24
28
|
$:.unshift(File.dirname(__FILE__)) unless
|
25
29
|
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
26
30
|
|
31
|
+
require 'rubygems'
|
32
|
+
unless Kernel.respond_to?(:gem)
|
33
|
+
Kernel.send :alias_method, :gem, :require_gem
|
34
|
+
end
|
35
|
+
|
27
36
|
unless defined?(ActiveSupport)
|
28
37
|
begin
|
29
38
|
$:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
|
30
39
|
require 'active_support'
|
31
40
|
rescue LoadError
|
32
|
-
|
33
|
-
require_gem 'activesupport'
|
41
|
+
gem 'activesupport'
|
34
42
|
end
|
35
43
|
end
|
36
44
|
|
@@ -39,8 +47,7 @@ unless defined?(ActiveRecord)
|
|
39
47
|
$:.unshift(File.dirname(__FILE__) + "/../../activerecord/lib")
|
40
48
|
require 'active_record'
|
41
49
|
rescue LoadError
|
42
|
-
|
43
|
-
require_gem 'activerecord'
|
50
|
+
gem 'activerecord'
|
44
51
|
end
|
45
52
|
end
|
46
53
|
|
@@ -51,15 +58,23 @@ unless defined?(ActionView)
|
|
51
58
|
require 'action_controller'
|
52
59
|
require 'action_view'
|
53
60
|
rescue LoadError
|
54
|
-
|
55
|
-
require_gem 'actionpack'
|
61
|
+
gem 'actionpack'
|
56
62
|
end
|
57
63
|
end
|
58
|
-
|
64
|
+
|
65
|
+
# Require 1.1.6 compatibility code if necessary
|
66
|
+
require 'active_record/version'
|
67
|
+
if ActiveRecord::VERSION::MAJOR < 1 || ActiveRecord::VERSION::MINOR < 15
|
68
|
+
require 'active_warehouse/compat/compat'
|
69
|
+
end
|
59
70
|
|
60
71
|
require 'active_warehouse/version'
|
61
72
|
require 'active_warehouse/core_ext'
|
62
|
-
require 'active_warehouse/
|
73
|
+
require 'active_warehouse/aggregate'
|
74
|
+
require 'active_warehouse/fact'
|
75
|
+
require 'active_warehouse/dimension'
|
76
|
+
require 'active_warehouse/cube'
|
77
|
+
require 'active_warehouse/report'
|
63
78
|
require 'active_warehouse/view'
|
64
79
|
require 'active_warehouse/builder'
|
65
80
|
require 'active_warehouse/migrations'
|
@@ -1,6 +1,6 @@
|
|
1
|
-
module ActiveWarehouse
|
2
|
-
# An aggreate within a cube used to store calculated values
|
3
|
-
#
|
1
|
+
module ActiveWarehouse #:nodoc:
|
2
|
+
# An aggreate within a cube used to store calculated values. Each aggregate will contain values for a dimension pair,
|
3
|
+
# down each of the dimension hierarchies
|
4
4
|
class Aggregate < ActiveRecord::Base
|
5
5
|
class << self
|
6
6
|
attr_accessor :name, :cube, :dimension1, :dimension2, :dimension1_hierarchy_name, :dimension2_hierarchy_name
|
@@ -68,6 +68,8 @@ module ActiveWarehouse
|
|
68
68
|
#connection.execute("TRUNCATE TABLE #{table_name}") #TODO: make this generic to support all databases
|
69
69
|
delete_all
|
70
70
|
|
71
|
+
$first = false
|
72
|
+
|
71
73
|
# aggregate the data for the two dimensions
|
72
74
|
fact_class = cube.fact_class
|
73
75
|
dim1 = Dimension.class_name(dimension1).constantize
|
@@ -81,29 +83,43 @@ module ActiveWarehouse
|
|
81
83
|
|
82
84
|
stmt, fields = build_query(fact_class, dim1, dim1_stage_path, dim2, dim2_stage_path)
|
83
85
|
|
86
|
+
puts "\nSTMT: #{stmt}" if $first
|
87
|
+
|
84
88
|
# Get the facts and aggregate them
|
85
|
-
|
89
|
+
# TODO: replace with select_all
|
90
|
+
fact_class.connection.select_all(stmt).each do |row|
|
91
|
+
require 'pp'
|
92
|
+
pp row if $first
|
86
93
|
dim1_value = []
|
87
|
-
dim1_stage_path.
|
88
|
-
dim1_value << row[
|
94
|
+
dim1_stage_path.each do |v|
|
95
|
+
dim1_value << row["#{v}"]
|
89
96
|
end
|
90
97
|
dim2_value = []
|
91
|
-
dim2_stage_path.
|
92
|
-
dim2_value << row[
|
98
|
+
dim2_stage_path.each do |v|
|
99
|
+
dim2_value << row["#{v}"]
|
93
100
|
end
|
94
101
|
|
95
102
|
agg_instance = new
|
96
103
|
agg_instance.dimension1_path = dim1_value.join(':')
|
97
104
|
agg_instance.dimension1_stage = dim1_stage_level
|
98
105
|
agg_instance.dimension2_path = dim2_value.join(':')
|
99
|
-
agg_instance.dimension2_stage = dim2_stage_level
|
100
|
-
|
106
|
+
agg_instance.dimension2_stage = dim2_stage_level
|
107
|
+
|
108
|
+
puts "DIM1_PATH: #{agg_instance.dimension1_path}" if $first
|
109
|
+
puts "DIM2_PATH: #{agg_instance.dimension2_path}" if $first
|
110
|
+
|
111
|
+
|
112
|
+
pp fields if $first
|
113
|
+
fields.each do |field|
|
101
114
|
# do the average here
|
102
|
-
|
115
|
+
puts "setting field #{field}, value is #{row[field.to_s]}" if $first
|
116
|
+
agg_instance.send("#{field}=".to_sym, row[field.to_s])
|
103
117
|
end
|
104
118
|
agg_instance.save!
|
105
119
|
|
106
120
|
meta_data.update_attribute(:populated_at, Time.now)
|
121
|
+
|
122
|
+
$first = false
|
107
123
|
end
|
108
124
|
end
|
109
125
|
end
|
@@ -148,9 +164,9 @@ module ActiveWarehouse
|
|
148
164
|
# put the SQL statement together
|
149
165
|
stmt = "select #{fact_find_options[:select]} from "
|
150
166
|
stmt << "#{fact_class.table_name} f #{fact_find_options[:joins]} "
|
151
|
-
stmt << "group by #{fact_find_options[:group]}"
|
167
|
+
stmt << "group by #{fact_find_options[:group]} "
|
152
168
|
|
153
|
-
return stmt, fields
|
169
|
+
return stmt.strip, fields
|
154
170
|
end
|
155
171
|
|
156
172
|
end
|