activewarehouse 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|