active_record_calculator 0.2.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # active\_record\_calculator
2
+
3
+ __Known Issues:__
4
+
5
+ * not compatible with activerecord 3.0.0 or higher
6
+
7
+ __Usage:__
8
+
9
+ Use active record calculator to subgroup SQL calculations. active record calculator is best used when:
10
+
11
+ * whenever more than a third of the table rows need to be examined
12
+ * when a group by returns many rows
13
+ * when the alternative requires many queries whose conditions can not be totally indexed
14
+
15
+ You should run benchmarks to determine when to use active record calculator. As a rule active record calculator queries are as slow as the slowest single query.
16
+
17
+ __Example:__
18
+
19
+ calculator = Purchase.calculator(:conditions => ["created_at > ?", 1.year.ago], group => "store_id") do |c|
20
+ c.count(:id, :total_purchases)
21
+ c.cnt(:id, :expensive_purchases, "amount_in_cents > 100")
22
+ c.average(:amount_in_cents, :cost_per_purchase)
23
+ c.avg(:amount_in_cents, :cost_per_expensive_purchase, "amount_in_cents > 100")
24
+ c.sum(:amount_in_cents, :total_sales)
25
+ c.sum(:amount_in_cents, :total_expensive_sales, "amount_in_cents > 100")
26
+ c.min(:amount_in_cents, :least_over_100, "amount_in_cents > 100")
27
+ c.max(:amount_in_cents, :most_less_100, "amount_in_cents <= 100")
28
+ end
29
+ calculator.calculate
30
+
31
+ __Will yield the same data as__
32
+
33
+ Purchase.count(:id, :conditions => ["created_at > ?", 1.year.ago], :group => "store_id")
34
+ Purchase.count(:id, :conditions => ["amount_in_cents > 100 and created_at > ?", 1.year.ago], :group => "store_id" )
35
+ Purchase.average(:amount_in_cents, :conditions => ["created_at > ?", 1.year.ago], :group => "store_id" )
36
+ Purchase.sum(:amount_in_cents, :conditions => ["created_at > ?", 1.year.ago])
37
+ Purchase.sum(:amount_in_cents, :conditions => ["amount_in_cents > 100 and created_at > ?", 1.year.ago])
38
+ Purchase.minimum(:amount_in_cents, :conditions => ["amount_in_cents > 100 and created_at > ?", 1.year.ago])
39
+ Purchase.maximum(:amount_in_cents, :conditions => ["amount_in_cents <= 100 and created_at > ?", 1.year.ago])
40
+
41
+ When adding operations, the calculator expects the format
42
+
43
+ method(column, alias, sub\_group\_condition)
44
+
45
+ The calculate method returns an array of hashes with the aliases as keys and results as values
46
+ Group columns are automatically included
47
+
48
+ __Example:__
49
+
50
+ >> calc = Transaction.calculator(:conditions => "transactions.user_id = 55555", :group => "transactions.user_id, bonus") do |c|
51
+ ?> c.count :id, "transactions_count"
52
+ >> c.count :id, "approved_bonus_count", "status = 'approved' and bonus = true"
53
+ >> c.count :id, "approved_offers_count", "status = 'approved' and bonus = false"
54
+ >> end
55
+ ...
56
+ >> calc.calculate
57
+ => [{"approved_bonus_count"=>0, "group_column_1"=>"55555", "group_column_2"=>"0", "transactions_count"=>34, "approved_offers_count"=>0}, {"approved_bonus_count"=>17, "group_column_1"=>"55555", "group_column_2"=>"1", "transactions_count"=>18, "approved_offers_count"=>17}]
58
+
59
+ You can use statement method to see the sql created.
60
+
61
+ The updater can be used for fast direct sql updates. An updater needs to be created where all the operations have aliases that have a respective column name in the update table. The first two arguments are the update table and the key to join. The key is joined with the first group column which is also required.
62
+
63
+ updater = Purchases.updater(:purchase_history, :user_id, :conditions => "created_at > '2011-07-01'", :group => "user_id")
64
+ updater.count :id, "total_purchases"
65
+ updater.cnt :id, "expensive_purchases", "price > 100"
66
+ updater.sum :price, "cheap_spending", "price < 100"
67
+ updater.avg :price, "cheap_average", "price < 100"
68
+ updater.min :price, "least_purchase"
69
+ updater.max :price, "most_purchase"
70
+
71
+ Update should work with all ActiveRecord supported databases except sqlite
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.version = ActiveRecordCalculator::VERSION
8
8
  s.authors = ["Grady Griffin"]
9
9
  s.email = ["gradyg@izea.com"]
10
- s.homepage = ""
10
+ s.homepage = "https://github.com/thegboat/active_record_calculator"
11
11
  s.summary = %q{ActiveRecord Calculations done faster}
12
12
  s.description = %q{active_record_calculator does groupable aggregate functions in one sql call for better performance}
13
13
 
@@ -22,10 +22,12 @@ Gem::Specification.new do |s|
22
22
  # s.add_development_dependency "rspec"
23
23
  # s.add_runtime_dependency "rest-client"
24
24
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
25
- s.add_runtime_dependency('activerecord', ">=0")
25
+ s.add_runtime_dependency('activerecord', "<3.0.0")
26
26
  s.add_development_dependency("rspec")
27
+ s.add_development_dependency("mysql")
27
28
  else
28
- s.add_dependency('activerecord', ">=0")
29
+ s.add_dependency('activerecord', "<3.0.0")
29
30
  s.add_development_dependency("rspec")
31
+ s.add_development_dependency("mysql")
30
32
  end
31
33
  end
@@ -82,8 +82,7 @@ module ActiveRecordCalculator
82
82
 
83
83
  def statement
84
84
  add_group_operations
85
- sql = @klass.send(:construct_finder_sql, @finder_options)
86
- sql.gsub(/^SELECT\s+\*/i, select)
85
+ construct_finder_sql.gsub(/^SELECT\s+\*/i, select)
87
86
  end
88
87
 
89
88
  private
@@ -118,10 +117,6 @@ module ActiveRecordCalculator
118
117
  @group_operations = []
119
118
  return unless @finder_options[:group]
120
119
  group_attrs = @finder_options[:group].to_s.split(',')
121
- if @finder_options[:for_update]
122
- @finder_options[:group] = group_attrs.first
123
- group_attrs = [group_attrs.first]
124
- end
125
120
  group_attrs.each_with_index do |grp, i|
126
121
  grp.downcase!
127
122
  grp.strip!
@@ -135,5 +130,23 @@ module ActiveRecordCalculator
135
130
  options = {:conditions => options} if options.is_a?(String)
136
131
  @operations << Operation.new(op, column_name, as, options)
137
132
  end
133
+
134
+ def construct_finder_sql
135
+ @klass.send(:construct_finder_sql, @finder_options)
136
+ end
137
+
138
+ # def sanitized_finder_params
139
+ # @finder_options[:conditions] = sanitize_sql_for_conditions(@finder_options[:conditions]) if @finder_options[:conditions]
140
+ # @finder_options[:group] = sanitize_sql(@finder_options[:group]) if @finder_options[:group]
141
+ # @finder_options[:having] = sanitize_sql(@finder_options[:having]) if @finder_options[:having]
142
+ # end
143
+
144
+ def sanitize_sql(ary)
145
+ ActiveRecord::Base.sanitize_sql(ary, table)
146
+ end
147
+
148
+ def sanitize_sql_for_conditions(condition)
149
+ ActiveRecord::Base.sanitize_sql_for_conditions(condition, table)
150
+ end
138
151
  end
139
152
  end
@@ -107,11 +107,11 @@ module ActiveRecordCalculator
107
107
  end
108
108
 
109
109
  def calculation_columns
110
- @calculation_columns ||= (calculator.columns + calculator.operations.collect(&:name))
110
+ calculator.columns.collect(&:alias_name) + calculator.operations.collect(&:name)
111
111
  end
112
112
 
113
113
  def invalid_columns
114
- @invalid_columns ||= calculation_columns - update_columns
114
+ calculation_columns - update_columns
115
115
  end
116
116
 
117
117
  def invalid_columns_sentence
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordCalculator
2
- VERSION = "0.2.0"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -1,72 +1,97 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe ActiveRecordCalculator::CalculatorProxy, "#new" do
4
- it "should return a ActiveRecordCalculator::CalculatorProxy instance" do
5
- instance = ActiveRecordCalculator::CalculatorProxy.new(FakeClass)
6
- instance.class.to_s.should eq('ActiveRecordCalculator::CalculatorProxy')
3
+ describe ActiveRecordCalculator::CalculatorProxy do
4
+ describe "#new" do
5
+ it "should return a ActiveRecordCalculator::CalculatorProxy instance" do
6
+ instance = ActiveRecordCalculator::CalculatorProxy.new(Purchase)
7
+ instance.class.to_s.should eq('ActiveRecordCalculator::CalculatorProxy')
8
+ end
7
9
  end
8
- end
9
10
 
10
- describe ActiveRecordCalculator::CalculatorProxy, "#col, #column" do
11
- it "should add a ActiveRecordCalculator::Column instance to columns" do
12
- instance = ActiveRecordCalculator::CalculatorProxy.new(FakeClass)
13
- instance.col(:id)
14
- instance.column(:id)
15
- instance.columns.length.should eq(2)
16
- instance.columns.first.class.should eq(ActiveRecordCalculator::Column)
11
+ describe "#col, #column" do
12
+ it "should add a ActiveRecordCalculator::Column instance to columns" do
13
+ instance = ActiveRecordCalculator::CalculatorProxy.new(Purchase)
14
+ instance.col(:id)
15
+ instance.column(:id)
16
+ instance.columns.length.should eq(2)
17
+ instance.columns.first.class.should eq(ActiveRecordCalculator::Column)
18
+ end
17
19
  end
18
- end
19
20
 
20
- describe ActiveRecordCalculator::CalculatorProxy, "#count, #cnt" do
21
- it "should add a ActiveRecordCalculator::Operation instance to operations" do
22
- instance = ActiveRecordCalculator::CalculatorProxy.new(FakeClass)
23
- instance.count(:id, 'counter')
24
- instance.cnt(:id, 'counter2')
25
- instance.operations.length.should eq(2)
26
- instance.operations.first.op.should eq(:count)
27
- instance.operations.first.class.should eq(ActiveRecordCalculator::Operation)
21
+ describe "#count, #cnt" do
22
+ it "should add a ActiveRecordCalculator::Operation instance to operations" do
23
+ instance = ActiveRecordCalculator::CalculatorProxy.new(Purchase)
24
+ instance.count(:id, 'counter')
25
+ instance.cnt(:id, 'counter2')
26
+ instance.operations.length.should eq(2)
27
+ instance.operations.first.op.should eq(:count)
28
+ instance.operations.first.class.should eq(ActiveRecordCalculator::Operation)
29
+ end
28
30
  end
29
- end
30
31
 
31
- describe ActiveRecordCalculator::CalculatorProxy, "#max, #maximum" do
32
- it "should add a ActiveRecordCalculator::Operation instance to operations" do
33
- instance = ActiveRecordCalculator::CalculatorProxy.new(FakeClass)
34
- instance.max(:id, 'counter')
35
- instance.maximum(:id, 'counter2')
36
- instance.operations.length.should eq(2)
37
- instance.operations.first.op.should eq(:max)
38
- instance.operations.first.class.should eq(ActiveRecordCalculator::Operation)
32
+ describe "#max, #maximum" do
33
+ it "should add a ActiveRecordCalculator::Operation instance to operations" do
34
+ instance = ActiveRecordCalculator::CalculatorProxy.new(Purchase)
35
+ instance.max(:id, 'counter')
36
+ instance.maximum(:id, 'counter2')
37
+ instance.operations.length.should eq(2)
38
+ instance.operations.first.op.should eq(:max)
39
+ instance.operations.first.class.should eq(ActiveRecordCalculator::Operation)
40
+ end
39
41
  end
40
- end
41
42
 
42
- describe ActiveRecordCalculator::CalculatorProxy, "#min, #minimum" do
43
- it "should add a ActiveRecordCalculator::Operation instance to operations" do
44
- instance = ActiveRecordCalculator::CalculatorProxy.new(FakeClass)
45
- instance.min(:id, 'counter')
46
- instance.minimum(:id, 'counter2')
47
- instance.operations.length.should eq(2)
48
- instance.operations.first.op.should eq(:min)
49
- instance.operations.first.class.should eq(ActiveRecordCalculator::Operation)
43
+ describe "#min, #minimum" do
44
+ it "should add a ActiveRecordCalculator::Operation instance to operations" do
45
+ instance = ActiveRecordCalculator::CalculatorProxy.new(Purchase)
46
+ instance.min(:id, 'counter')
47
+ instance.minimum(:id, 'counter2')
48
+ instance.operations.length.should eq(2)
49
+ instance.operations.first.op.should eq(:min)
50
+ instance.operations.first.class.should eq(ActiveRecordCalculator::Operation)
51
+ end
50
52
  end
51
- end
52
53
 
53
- describe ActiveRecordCalculator::CalculatorProxy, "#avg, #average" do
54
- it "should add a ActiveRecordCalculator::Operation instance to operations" do
55
- instance = ActiveRecordCalculator::CalculatorProxy.new(FakeClass)
56
- instance.avg(:id, 'counter')
57
- instance.average(:id, 'counter2')
58
- instance.operations.length.should eq(2)
59
- instance.operations.first.op.should eq(:avg)
60
- instance.operations.first.class.should eq(ActiveRecordCalculator::Operation)
54
+ describe "#avg, #average" do
55
+ it "should add a ActiveRecordCalculator::Operation instance to operations" do
56
+ instance = ActiveRecordCalculator::CalculatorProxy.new(Purchase)
57
+ instance.avg(:id, 'counter')
58
+ instance.average(:id, 'counter2')
59
+ instance.operations.length.should eq(2)
60
+ instance.operations.first.op.should eq(:avg)
61
+ instance.operations.first.class.should eq(ActiveRecordCalculator::Operation)
62
+ end
61
63
  end
62
- end
63
64
 
64
- describe ActiveRecordCalculator::CalculatorProxy, "#sum" do
65
- it "should add a ActiveRecordCalculator::Operation instance to operations" do
66
- instance = ActiveRecordCalculator::CalculatorProxy.new(FakeClass)
67
- instance.sum(:id, 'counter')
68
- instance.operations.length.should eq(1)
69
- instance.operations.first.op.should eq(:sum)
70
- instance.operations.first.class.should eq(ActiveRecordCalculator::Operation)
65
+ describe "#sum" do
66
+ it "should add a ActiveRecordCalculator::Operation instance to operations" do
67
+ instance = ActiveRecordCalculator::CalculatorProxy.new(Purchase)
68
+ instance.sum(:id, 'counter')
69
+ instance.operations.length.should eq(1)
70
+ instance.operations.first.op.should eq(:sum)
71
+ instance.operations.first.class.should eq(ActiveRecordCalculator::Operation)
72
+ end
73
+ end
74
+
75
+ describe "#calculate" do
76
+ it "should calculate as base aggregate methods" do
77
+ instance = ActiveRecordCalculator::CalculatorProxy.new(Purchase)
78
+ instance.count(:id, :total_purchases)
79
+ instance.count(:id, :expensive_purchases, "amount_in_cents > 100")
80
+ instance.avg(:amount_in_cents, :cost_per_purchase)
81
+ instance.avg(:amount_in_cents, :cost_per_expensive_purchase, "amount_in_cents > 100")
82
+ instance.sum(:amount_in_cents, :total_sales)
83
+ instance.sum(:amount_in_cents, :total_expensive_sales, "amount_in_cents > 100")
84
+ instance.min(:amount_in_cents, :least_over_100, "amount_in_cents > 100")
85
+ instance.max(:amount_in_cents, :most_less_100, "amount_in_cents <= 100")
86
+ rtn = instance.calculate.first
87
+ rtn['total_purchases'].should eq(Purchase.count)
88
+ rtn['expensive_purchases'].should eq(Purchase.count(:id, :conditions => "amount_in_cents > 100"))
89
+ rtn['cost_per_purchase'].should eq(Purchase.average(:amount_in_cents))
90
+ rtn['cost_per_expensive_purchase'].should eq(Purchase.average(:amount_in_cents, :conditions => "amount_in_cents > 100"))
91
+ rtn['total_sales'].should eq(Purchase.sum(:amount_in_cents))
92
+ rtn['total_expensive_sales'].should eq(Purchase.sum(:amount_in_cents, :conditions => "amount_in_cents > 100"))
93
+ rtn['least_over_100'].should eq(Purchase.minimum(:amount_in_cents, :conditions => "amount_in_cents > 100"))
94
+ rtn['most_less_100'].should eq(Purchase.maximum(:amount_in_cents, :conditions => "amount_in_cents <= 100"))
95
+ end
71
96
  end
72
97
  end
data/spec/spec_helper.rb CHANGED
@@ -3,17 +3,61 @@ require 'bundler/setup'
3
3
 
4
4
  require 'active_record_calculator'
5
5
 
6
- class FakeConnection
6
+
7
+ RSpec.configure do |config|
8
+ # some (optional) config here
7
9
  end
8
10
 
9
- class FakeClass
10
-
11
- def table_name
12
- "some_records"
13
- end
14
-
11
+ ActiveRecord::Base.establish_connection(
12
+ :adapter => "mysql",
13
+ :database => "active_record_calculator_test"
14
+ )
15
+
16
+ ActiveRecord::Migration.create_table :purchases, :force => true do |t|
17
+ t.integer :store_id, :null => false
18
+ t.integer :amount_in_cents, :null => false, :default => 0
19
+ t.boolean :used_coupon, :null => false, :default => false
20
+ t.datetime :purchase_date, :null => false
15
21
  end
16
22
 
17
- RSpec.configure do |config|
18
- # some (optional) config here
19
- end
23
+ ActiveRecord::Migration.create_table :stat_summaries, :force => true do |t|
24
+ t.integer :store_id, :null => false
25
+ t.integer :total_purchases, :null => false, :default => 0
26
+ t.decimal :cost_per_purchase, :precision => 14, :scale => 2, :default => 0.0, :null => false
27
+ t.integer :total_sales, :null => false, :default => 0
28
+ t.integer :purchases_with_coupon, :null => false, :default => 0
29
+ end
30
+
31
+ ActiveRecord::Migration.create_table :stores, :force => true do |t|
32
+ t.string :name
33
+ end
34
+
35
+ class Purchase < ActiveRecord::Base
36
+ belongs_to :store
37
+ end
38
+
39
+ class Store < ActiveRecord::Base
40
+ has_many :purchases
41
+ has_one :stat_summary
42
+ end
43
+
44
+ class StatSummary < ActiveRecord::Base
45
+ belongs_to :store
46
+ end
47
+
48
+ store_ids = 1.upto(5).collect do |n|
49
+ store = Store.create(:name => "Store_#{n}")
50
+ StatSummary.create(:store_id => store.id)
51
+ store.id
52
+ end
53
+
54
+ 1.upto(1000).each do |n|
55
+ Purchase.create(
56
+ :store_id => store_ids.shuffle.first,
57
+ :used_coupon => rand(2) == 1,
58
+ :amount_in_cents => rand(2)*100 + rand(100),
59
+ :purchase_date => (rand(365) + 1).days.ago
60
+ )
61
+ end
62
+
63
+
@@ -1 +1,114 @@
1
- require 'spec_helper'
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecordCalculator::UpdaterProxy do
4
+ describe "#new" do
5
+ it "should raise exception if no group on calculator" do
6
+ calculator = ActiveRecordCalculator::CalculatorProxy.new(Purchase)
7
+ lambda {
8
+ ActiveRecordCalculator::UpdaterProxy.new(:stat_summaries, :store_id, calculator)
9
+ }.should raise_error(ActiveRecordCalculator::NoUpdateKeyError)
10
+ end
11
+
12
+ it "should raise exception if there is an alias for the calculator that does not match the updater" do
13
+ calculator = ActiveRecordCalculator::CalculatorProxy.new(Purchase)
14
+ calculator.count(:id, :not_existent_column)
15
+ lambda {
16
+ ActiveRecordCalculator::UpdaterProxy.new(:stat_summaries, :store_id, calculator)
17
+ }.should raise_error(ActiveRecordCalculator::InvalidColumnError)
18
+ end
19
+
20
+ it "should return a ActiveRecordCalculator::UpdaterProxy instance" do
21
+ calculator = ActiveRecordCalculator::CalculatorProxy.new(Purchase, {:group => "purchases.store_id"})
22
+ instance = ActiveRecordCalculator::UpdaterProxy.new(:stat_summaries, :store_id, calculator)
23
+ instance.class.to_s.should eq('ActiveRecordCalculator::UpdaterProxy')
24
+ end
25
+ end
26
+
27
+ describe "#col, #column" do
28
+ it "should add a ActiveRecordCalculator::Column instance to columns" do
29
+ calculator = ActiveRecordCalculator::CalculatorProxy.new(Purchase, {:group => "purchases.store_id"})
30
+ instance = ActiveRecordCalculator::UpdaterProxy.new(:stat_summaries, :store_id, calculator)
31
+ instance.col(:id)
32
+ instance.column(:id)
33
+ instance.send(:calculator).columns.length.should eq(2)
34
+ instance.send(:calculator).columns.first.class.should eq(ActiveRecordCalculator::Column)
35
+ end
36
+ end
37
+
38
+ describe "#count, #cnt" do
39
+ it "should add a ActiveRecordCalculator::Operation instance to operations" do
40
+ calculator = ActiveRecordCalculator::CalculatorProxy.new(Purchase, {:group => "purchases.store_id"})
41
+ instance = ActiveRecordCalculator::UpdaterProxy.new(:stat_summaries, :store_id, calculator)
42
+ instance.count(:id, 'counter')
43
+ instance.cnt(:id, 'counter2')
44
+ instance.send(:calculator).operations.length.should eq(2)
45
+ instance.send(:calculator).operations.first.op.should eq(:count)
46
+ instance.send(:calculator).operations.first.class.should eq(ActiveRecordCalculator::Operation)
47
+ end
48
+ end
49
+
50
+ describe "#max, #maximum" do
51
+ it "should add a ActiveRecordCalculator::Operation instance to operations" do
52
+ calculator = ActiveRecordCalculator::CalculatorProxy.new(Purchase, {:group => "purchases.store_id"})
53
+ instance = ActiveRecordCalculator::UpdaterProxy.new(:stat_summaries, :store_id, calculator)
54
+ instance.max(:id, 'counter')
55
+ instance.maximum(:id, 'counter2')
56
+ instance.send(:calculator).operations.length.should eq(2)
57
+ instance.send(:calculator).operations.first.op.should eq(:max)
58
+ instance.send(:calculator).operations.first.class.should eq(ActiveRecordCalculator::Operation)
59
+ end
60
+ end
61
+
62
+ describe "#min, #minimum" do
63
+ it "should add a ActiveRecordCalculator::Operation instance to operations" do
64
+ calculator = ActiveRecordCalculator::CalculatorProxy.new(Purchase, {:group => "purchases.store_id"})
65
+ instance = ActiveRecordCalculator::UpdaterProxy.new(:stat_summaries, :store_id, calculator)
66
+ instance.min(:id, 'counter')
67
+ instance.minimum(:id, 'counter2')
68
+ instance.send(:calculator).operations.length.should eq(2)
69
+ instance.send(:calculator).operations.first.op.should eq(:min)
70
+ instance.send(:calculator).operations.first.class.should eq(ActiveRecordCalculator::Operation)
71
+ end
72
+ end
73
+
74
+ describe "#avg, #average" do
75
+ it "should add a ActiveRecordCalculator::Operation instance to operations" do
76
+ calculator = ActiveRecordCalculator::CalculatorProxy.new(Purchase, {:group => "purchases.store_id"})
77
+ instance = ActiveRecordCalculator::UpdaterProxy.new(:stat_summaries, :store_id, calculator)
78
+ instance.avg(:id, 'counter')
79
+ instance.average(:id, 'counter2')
80
+ instance.send(:calculator).operations.length.should eq(2)
81
+ instance.send(:calculator).operations.first.op.should eq(:avg)
82
+ instance.send(:calculator).operations.first.class.should eq(ActiveRecordCalculator::Operation)
83
+ end
84
+ end
85
+
86
+ describe "#sum" do
87
+ it "should add a ActiveRecordCalculator::Operation instance to operations" do
88
+ calculator = ActiveRecordCalculator::CalculatorProxy.new(Purchase, {:group => "purchases.store_id"})
89
+ instance = ActiveRecordCalculator::UpdaterProxy.new(:stat_summaries, :store_id, calculator)
90
+ instance.sum(:id, 'counter')
91
+ instance.send(:calculator).operations.length.should eq(1)
92
+ instance.send(:calculator).operations.first.op.should eq(:sum)
93
+ instance.send(:calculator).operations.first.class.should eq(ActiveRecordCalculator::Operation)
94
+ end
95
+ end
96
+
97
+ describe "#update" do
98
+ it "should update the specified table" do
99
+ calculator = ActiveRecordCalculator::CalculatorProxy.new(Purchase, {:group => "purchases.store_id"})
100
+ instance = ActiveRecordCalculator::UpdaterProxy.new(:stat_summaries, :store_id, calculator)
101
+ instance.count(:id, :total_purchases)
102
+ instance.avg(:amount_in_cents, :cost_per_purchase)
103
+ instance.sum(:amount_in_cents, :total_sales)
104
+ instance.count(:id, :purchases_with_coupon, "used_coupon = true")
105
+ instance.update
106
+ StatSummary.all.each do |stat_summary|
107
+ stat_summary.total_purchases.should eq(Purchase.count(:id, :conditions => {:store_id => stat_summary.store_id}))
108
+ stat_summary.cost_per_purchase.round(2).should eq(Purchase.average(:amount_in_cents, :conditions => {:store_id => stat_summary.store_id}).round(2))
109
+ stat_summary.total_sales.should eq(Purchase.sum(:amount_in_cents, :conditions => {:store_id => stat_summary.store_id}))
110
+ stat_summary.purchases_with_coupon.should eq(Purchase.count(:id, :conditions => {:store_id => stat_summary.store_id, :used_coupon => true}))
111
+ end
112
+ end
113
+ end
114
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_calculator
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 59
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 2
8
+ - 9
9
9
  - 0
10
- version: 0.2.0
10
+ version: 0.9.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Grady Griffin
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-02-01 00:00:00 Z
18
+ date: 2012-02-07 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: activerecord
@@ -23,12 +23,14 @@ dependencies:
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
24
  none: false
25
25
  requirements:
26
- - - ">="
26
+ - - <
27
27
  - !ruby/object:Gem::Version
28
- hash: 3
28
+ hash: 7
29
29
  segments:
30
+ - 3
30
31
  - 0
31
- version: "0"
32
+ - 0
33
+ version: 3.0.0
32
34
  type: :runtime
33
35
  version_requirements: *id001
34
36
  - !ruby/object:Gem::Dependency
@@ -45,6 +47,20 @@ dependencies:
45
47
  version: "0"
46
48
  type: :development
47
49
  version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: mysql
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :development
63
+ version_requirements: *id003
48
64
  description: active_record_calculator does groupable aggregate functions in one sql call for better performance
49
65
  email:
50
66
  - gradyg@izea.com
@@ -57,7 +73,7 @@ extra_rdoc_files: []
57
73
  files:
58
74
  - .gitignore
59
75
  - Gemfile
60
- - README
76
+ - README.md
61
77
  - Rakefile
62
78
  - active_record_calculator.gemspec
63
79
  - lib/active_record_calculator.rb
@@ -74,7 +90,7 @@ files:
74
90
  - spec/operation_spec.rb
75
91
  - spec/spec_helper.rb
76
92
  - spec/updater_proxy_spec.rb
77
- homepage: ""
93
+ homepage: https://github.com/thegboat/active_record_calculator
78
94
  licenses: []
79
95
 
80
96
  post_install_message:
data/README DELETED
@@ -1,58 +0,0 @@
1
- Use active_record_calculator to perform subgrouping of aggregate calculations
2
-
3
- Example:
4
-
5
- calculator = Purchases.calculator(:conditions => "created_at > '2011-07-01'", :group => "user_id") do |c|
6
- c.count :id, "total"
7
- c.cnt :id, "expensive", "price > 100" #cnt is an alias for count
8
- c.sum :price, "cheap_spending", "price < 100"
9
- c.avg :price, "cheap_average", "price < 100" #avg is an alias for average
10
- c.min :price, "least"
11
- c.max :price, "most" #max is an alias for maximum
12
- c.col :item_name #col is an alias for column; adds a column to the result
13
- end
14
-
15
- calculator.calculate
16
-
17
- OR
18
-
19
- calculator = Purchases.calculator(:conditions => "created_at > '2011-07-01'", :group => "user_id")
20
- calculator.count :id, "total"
21
- calculator.cnt :id, "expensive", "price > 100"
22
- calculator.sum :price, "cheap_spending", "price < 100"
23
- calculator.avg :price, "cheap_average", "price < 100"
24
- calculator.min :price, "least"
25
- calculator.max :price, "most"
26
- calculator.calculate
27
-
28
- When adding operations, the calculator expects the format
29
-
30
- method(column, alias, sub_group_condition)
31
-
32
- The calculate method yields an array of hashes with the aliases as keys and results as values
33
- Group columns are automatically included
34
-
35
- Example:
36
-
37
- >> calc = Transaction.calculator(:conditions => "transactions.user_id = 55555", :group => "transactions.user_id, bonus") do |c|
38
- ?> c.count :id, "transactions_count"
39
- >> c.count :id, "approved_bonus_count", "status = 'approved' and bonus = true"
40
- >> c.count :id, "approved_offers_count", "status = 'approved' and bonus = false"
41
- >> end
42
- ...
43
- >> calc.calculate
44
- => [{"approved_bonus_count"=>0, "group_column_1"=>"55555", "group_column_2"=>"0", "transactions_count"=>34, "approved_offers_count"=>0}, {"approved_bonus_count"=>17, "group_column_1"=>"55555", "group_column_2"=>"1", "transactions_count"=>18, "approved_offers_count"=>17}]
45
-
46
- You can use statement to see the sql created
47
-
48
- The updater can be used for fast direct sql updates. An updater needs to be created where all the operations have aliases that have a respective column name in the update table. The first two arguments are the update table and the key to join. The key is joined with the first group column which is also required.
49
-
50
- updater = Purchases.updater(:purchase_history, :user_id, :conditions => "created_at > '2011-07-01'", :group => "user_id")
51
- updater.count :id, "total_purchases"
52
- updater.cnt :id, "expensive_purchases", "price > 100"
53
- updater.sum :price, "cheap_spending", "price < 100"
54
- updater.avg :price, "cheap_average", "price < 100"
55
- updater.min :price, "least_purchase"
56
- updater.max :price, "most_purchase"
57
-
58
- Update should work with all ActiveRecord supported databases except sqlite