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 +71 -0
- data/active_record_calculator.gemspec +5 -3
- data/lib/active_record_calculator/calculator_proxy.rb +19 -6
- data/lib/active_record_calculator/updater_proxy.rb +2 -2
- data/lib/active_record_calculator/version.rb +1 -1
- data/spec/calculator_proxy_spec.rb +81 -56
- data/spec/spec_helper.rb +54 -10
- data/spec/updater_proxy_spec.rb +114 -1
- metadata +25 -9
- data/README +0 -58
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', "
|
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', "
|
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
|
-
|
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
|
-
|
110
|
+
calculator.columns.collect(&:alias_name) + calculator.operations.collect(&:name)
|
111
111
|
end
|
112
112
|
|
113
113
|
def invalid_columns
|
114
|
-
|
114
|
+
calculation_columns - update_columns
|
115
115
|
end
|
116
116
|
|
117
117
|
def invalid_columns_sentence
|
@@ -1,72 +1,97 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe ActiveRecordCalculator::CalculatorProxy
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
# some (optional) config here
|
7
9
|
end
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
+
|
data/spec/updater_proxy_spec.rb
CHANGED
@@ -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:
|
4
|
+
hash: 59
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 9
|
9
9
|
- 0
|
10
|
-
version: 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-
|
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:
|
28
|
+
hash: 7
|
29
29
|
segments:
|
30
|
+
- 3
|
30
31
|
- 0
|
31
|
-
|
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
|