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.
Files changed (44) hide show
  1. data/README +62 -17
  2. data/Rakefile +17 -0
  3. data/generators/bridge/USAGE +1 -0
  4. data/generators/bridge/bridge_generator.rb +46 -0
  5. data/generators/bridge/templates/fixture.yml +5 -0
  6. data/generators/bridge/templates/migration.rb +20 -0
  7. data/generators/bridge/templates/model.rb +3 -0
  8. data/generators/dimension/templates/unit_test.rb +0 -2
  9. data/generators/dimension_view/USAGE +1 -0
  10. data/generators/dimension_view/dimension_view_generator.rb +62 -0
  11. data/generators/dimension_view/templates/migration.rb +11 -0
  12. data/generators/dimension_view/templates/model.rb +3 -0
  13. data/generators/dimension_view/templates/unit_test.rb +10 -0
  14. data/init.rb +1 -0
  15. data/lib/active_warehouse.rb +24 -9
  16. data/lib/active_warehouse/{model/aggregate.rb → aggregate.rb} +29 -13
  17. data/lib/active_warehouse/builder/date_dimension_builder.rb +21 -6
  18. data/lib/active_warehouse/builder/random_data_builder.rb +204 -3
  19. data/lib/active_warehouse/compat/compat.rb +49 -0
  20. data/lib/active_warehouse/{model/cube.rb → cube.rb} +47 -17
  21. data/lib/active_warehouse/dimension.rb +296 -0
  22. data/lib/active_warehouse/dimension/bridge.rb +15 -0
  23. data/lib/active_warehouse/dimension/dimension_view.rb +11 -0
  24. data/lib/active_warehouse/dimension/hierarchical_dimension.rb +60 -0
  25. data/lib/active_warehouse/dimension/slowly_changing_dimension.rb +137 -0
  26. data/lib/active_warehouse/{model/fact.rb → fact.rb} +45 -10
  27. data/lib/active_warehouse/migrations.rb +1 -2
  28. data/lib/active_warehouse/report.rb +3 -0
  29. data/lib/active_warehouse/{model/report → report}/abstract_report.rb +0 -0
  30. data/lib/active_warehouse/{model/report → report}/chart_report.rb +0 -0
  31. data/lib/active_warehouse/{model/report → report}/table_report.rb +0 -0
  32. data/lib/active_warehouse/version.rb +2 -2
  33. data/lib/active_warehouse/view/report_helper.rb +2 -1
  34. data/tasks/active_warehouse_tasks.rake +54 -0
  35. metadata +43 -21
  36. data/doc/agg_queries.txt +0 -26
  37. data/doc/agg_queries_results.txt +0 -150
  38. data/doc/queries.txt +0 -35
  39. data/lib/active_warehouse/model.rb +0 -5
  40. data/lib/active_warehouse/model/dimension.rb +0 -3
  41. data/lib/active_warehouse/model/dimension/bridge.rb +0 -32
  42. data/lib/active_warehouse/model/dimension/dimension.rb +0 -152
  43. data/lib/active_warehouse/model/dimension/hierarchical_dimension.rb +0 -35
  44. 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. If you install it as a gem you must include the following in your config/environment.rb:
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
- Additionally, to use the Rake tasks you must include the following in your Rakefile:
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
- load 'tasks/active_warehouse_tasks.rake'
26
+ rake gems:link GEM=activewarehouse
27
+
28
+ == Generators
11
29
 
12
- Generators
13
- ===============
14
30
  ActiveWarehouse comes with several generators
15
31
 
16
- script/generate fact Sales
17
- script/generate fact sales
32
+ script/generate fact Sales
33
+ script/generate fact sales
18
34
 
19
- Creates a SalesFact class and a sales_facts table.
35
+ Creates a SalesFact class and a sales_facts table.
20
36
 
21
- script/generate dimension Region
22
- script/generate dimension region
37
+ script/generate dimension Region
38
+ script/generate dimension region
23
39
 
24
- Creates a RegionDimension class and a region_dimension table.
40
+ Creates a RegionDimension class and a region_dimension table.
25
41
 
26
- script/generate cube RegionalSales
27
- script/generate cube regional_sales
42
+ script/generate cube RegionalSales
43
+ script/generate cube regional_sales
28
44
 
29
- Creates a RegionalSalesCube class.
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,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 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,3 @@
1
+ class <%= class_name %> < ActiveWarehouse::Bridge
2
+
3
+ end
@@ -1,8 +1,6 @@
1
1
  require File.dirname(__FILE__) + '<%= '/..' * class_nesting_depth %>/../test_helper'
2
2
 
3
3
  class <%= class_name %>Test < Test::Unit::TestCase
4
- fixtures :<%= table_name %>
5
-
6
4
  # Replace this with your real tests.
7
5
  def test_truth
8
6
  assert true
@@ -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
@@ -0,0 +1,11 @@
1
+ class <%= migration_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_view "<%= view_name %>", <%= view_query do |t|
4
+ <%= view_attributes %>
5
+ end
6
+ end
7
+
8
+ def self.down
9
+ drop_view "<%= view_name %>"
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ class <%= class_name %> < ActiveWarehouse::DimensionView
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 :<%= view_name %>
5
+
6
+ # Replace this with your real tests.
7
+ def test_truth
8
+ assert true
9
+ end
10
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'active_warehouse'
@@ -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
- require 'rubygems'
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
- require 'rubygems'
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
- require 'rubygems'
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/model'
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
- # Each aggregate will contain values for a dimension pair, down each of the dimension hierarchies
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
- fact_class.connection.execute(stmt).each do |row|
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.each_with_index do |v, index|
88
- dim1_value << row[index]
94
+ dim1_stage_path.each do |v|
95
+ dim1_value << row["#{v}"]
89
96
  end
90
97
  dim2_value = []
91
- dim2_stage_path.each_with_index do |v, index|
92
- dim2_value << row[dim1_stage_path.length + index]
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
- fields.each_with_index do |field, index|
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
- agg_instance.send("#{field}=".to_sym, row[index + dim1_value.length + dim2_value.length])
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