activerecord-futures 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/.travis.yml +20 -0
  2. data/Gemfile +11 -1
  3. data/README.md +43 -23
  4. data/Rakefile +17 -0
  5. data/activerecord-futures.gemspec +3 -1
  6. data/lib/active_record/connection_adapters/future_enabled.rb +34 -0
  7. data/lib/active_record/connection_adapters/future_enabled_mysql2_adapter.rb +23 -22
  8. data/lib/active_record/connection_adapters/future_enabled_postgresql_adapter.rb +52 -0
  9. data/lib/active_record/futures.rb +10 -9
  10. data/lib/active_record/futures/delegation.rb +1 -0
  11. data/lib/active_record/futures/future.rb +22 -6
  12. data/lib/active_record/futures/future_calculation.rb +4 -12
  13. data/lib/active_record/futures/future_calculation_array.rb +8 -0
  14. data/lib/active_record/futures/future_calculation_value.rb +11 -0
  15. data/lib/active_record/futures/future_relation.rb +12 -11
  16. data/lib/active_record/futures/proxy.rb +37 -0
  17. data/lib/active_record/futures/query_recording.rb +12 -26
  18. data/lib/activerecord-futures.rb +3 -0
  19. data/lib/activerecord-futures/version.rb +1 -1
  20. data/spec/active_record/futures/future_relation_spec.rb +9 -1
  21. data/spec/active_record/futures/proxy_spec.rb +51 -0
  22. data/spec/db/schema.rb +15 -6
  23. data/spec/in_action/combination_of_futures_spec.rb +153 -0
  24. data/spec/in_action/future_count_execution_spec.rb +98 -0
  25. data/spec/in_action/future_fulfillment_spec.rb +10 -7
  26. data/spec/in_action/future_pluck_execution_spec.rb +44 -0
  27. data/spec/in_action/future_relation_execution_spec.rb +52 -0
  28. data/spec/models/comment.rb +4 -0
  29. data/spec/models/post.rb +3 -0
  30. data/spec/models/user.rb +1 -0
  31. data/spec/spec_helper.rb +45 -9
  32. data/spec/support/matchers/exec.rb +66 -0
  33. data/spec/support/matchers/exec_query.rb +23 -0
  34. metadata +60 -6
  35. data/spec/models/country.rb +0 -2
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ describe "future_count method" do
4
+ context "single value count" do
5
+ let(:relation) { Post.where("published_at < ?", Time.new(2013, 1, 1)) }
6
+ let(:count) { relation.future_count }
7
+ let(:count_sql) do
8
+ arel = relation.arel
9
+ arel.projections = []
10
+ arel.project("COUNT(*)")
11
+ arel.to_sql
12
+ end
13
+
14
+ before do
15
+ Post.create(published_at: Time.new(2012, 12, 10))
16
+ Post.create(published_at: Time.new(2012, 6, 23))
17
+ Post.create(published_at: Time.new(2013, 4, 5))
18
+ end
19
+
20
+ describe "#value" do
21
+ let(:calling_value) { -> { count.value } }
22
+
23
+ specify do
24
+ calling_value.should exec(1).query
25
+ end
26
+
27
+ specify do
28
+ calling_value.should exec_query(count_sql)
29
+ end
30
+
31
+ specify { count.value.should eq 2 }
32
+
33
+ context "executing it twice" do
34
+ before do
35
+ count.value
36
+ end
37
+
38
+ specify do
39
+ calling_value.should exec(0).queries
40
+ end
41
+
42
+ specify { count.value.should eq 2 }
43
+ end
44
+ end
45
+ end
46
+
47
+ context "grouped value count" do
48
+ let(:relation) { Comment.scoped }
49
+ let(:count) { relation.future_count(group: :post_id) }
50
+ let(:count_sql) do
51
+ arel = relation.arel
52
+ arel.projections = []
53
+ arel.project("COUNT(*) AS count_all")
54
+ arel.project("post_id AS post_id")
55
+ arel.group("post_id")
56
+ arel.to_sql
57
+ end
58
+
59
+ let(:post_1) { Post.create(published_at: Time.now) }
60
+ let(:post_2) { Post.create(published_at: Time.now) }
61
+
62
+ before do
63
+ Comment.create(post: post_1)
64
+ Comment.create(post: post_1)
65
+ Comment.create(post: post_2)
66
+ Comment.create(post: post_2)
67
+ Comment.create(post: post_2)
68
+ end
69
+
70
+ describe "#to_a" do
71
+ let(:calling_to_a) { -> { count.to_a } }
72
+
73
+ specify do
74
+ calling_to_a.should exec(1).query
75
+ end
76
+
77
+ specify do
78
+ calling_to_a.should exec_query(count_sql)
79
+ end
80
+
81
+ specify { count.to_a[post_1.id].should eq 2 }
82
+ specify { count.to_a[post_2.id].should eq 3 }
83
+
84
+ context "executing it twice" do
85
+ before do
86
+ count.to_a
87
+ end
88
+
89
+ specify do
90
+ calling_to_a.should exec(0).queries
91
+ end
92
+
93
+ specify { count.to_a[post_1.id].should eq 2 }
94
+ specify { count.to_a[post_2.id].should eq 3 }
95
+ end
96
+ end
97
+ end
98
+ end
@@ -5,7 +5,7 @@ module ActiveRecord::Futures
5
5
  subject { Future }
6
6
 
7
7
  context "when futurizing a relation" do
8
- let!(:future) { User.where(name: "").future }
8
+ let!(:future) { Post.where(title: "Some post").future }
9
9
 
10
10
  its(:all) { should have(1).future }
11
11
 
@@ -21,14 +21,15 @@ module ActiveRecord::Futures
21
21
  context "the future" do
22
22
  subject { future }
23
23
 
24
- it { should be_fulfilled }
24
+ it(nil, :supporting_adapter) { should be_fulfilled }
25
+ it(nil, :not_supporting_adapter) { should_not be_fulfilled }
25
26
  end
26
27
  end
27
28
  end
28
29
 
29
30
  context "when futurizing two relations" do
30
- let!(:future) { User.where(name: "").future }
31
- let!(:another_future) { Country.where(name: "").future }
31
+ let!(:future) { Post.where(title: "Some post").future }
32
+ let!(:another_future) { User.where(name: "Lenny").future }
32
33
 
33
34
  its(:all) { should have(2).futures }
34
35
 
@@ -46,16 +47,18 @@ module ActiveRecord::Futures
46
47
  its(:all) { should have(0).futures }
47
48
 
48
49
  context "the first relation future" do
49
- specify { future.should be_fulfilled }
50
+ specify(nil, :supporting_adapter) { future.should be_fulfilled }
51
+ specify(nil, :not_supporting_adapter) { future.should_not be_fulfilled }
50
52
  end
51
53
 
52
54
  context "the other relation future" do
53
- specify { another_future.should be_fulfilled }
55
+ specify(nil, :supporting_adapter) { another_future.should be_fulfilled }
56
+ specify(nil, :not_supporting_adapter) { another_future.should_not be_fulfilled }
54
57
  end
55
58
  end
56
59
 
57
60
  context "and executing another non futurized relation" do
58
- let!(:normal_relation) { User.where(name: "") }
61
+ let!(:normal_relation) { User.where(name: "John") }
59
62
  before { normal_relation.to_a }
60
63
 
61
64
  its(:all) { should have(2).futures }
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe "future_pluck method" do
4
+ let(:relation) { Post.where("published_at < ?", Time.new(2013, 1, 1)) }
5
+ let(:pluck) { relation.future_pluck('title') }
6
+ let(:pluck_sql) do
7
+ arel = relation.arel
8
+ arel.projections = []
9
+ arel.project('title')
10
+ arel.to_sql
11
+ end
12
+
13
+ before do
14
+ Post.create(title: "Post 1", published_at: Time.new(2012, 12, 10))
15
+ Post.create(title: "Post 2", published_at: Time.new(2012, 6, 23))
16
+ Post.create(title: "Post 3", published_at: Time.new(2013, 4, 5))
17
+ end
18
+
19
+ describe "#to_a" do
20
+ let(:calling_to_a) { -> { pluck.to_a } }
21
+
22
+ specify do
23
+ calling_to_a.should exec(1).query
24
+ end
25
+
26
+ specify do
27
+ calling_to_a.should exec_query(pluck_sql)
28
+ end
29
+
30
+ specify { pluck.to_a.should eq ["Post 1", "Post 2"] }
31
+
32
+ context "executing it twice" do
33
+ before do
34
+ pluck.to_a
35
+ end
36
+
37
+ specify do
38
+ calling_to_a.should exec(0).queries
39
+ end
40
+
41
+ specify { pluck.to_a.should eq ["Post 1", "Post 2"] }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,52 @@
1
+ require "spec_helper"
2
+
3
+ describe "future method" do
4
+
5
+ before do
6
+ Post.create(published_at: Time.new(2012, 12, 10))
7
+ Post.create(published_at: Time.new(2012, 6, 23))
8
+ Post.create(published_at: Time.new(2013, 4, 5))
9
+ end
10
+
11
+ def self.spec_relation(description, relation_lambda, &block)
12
+ context "with a sample relation that #{description}" do
13
+ let(:relation) { relation_lambda.call }
14
+ let(:future) { relation.future }
15
+
16
+ describe "#to_a" do
17
+ let(:calling_to_a) { lambda { future.to_a } }
18
+
19
+ specify do
20
+ calling_to_a.should exec(1).query
21
+ end
22
+
23
+ specify do
24
+ calling_to_a.should exec_query(relation.to_sql)
25
+ end
26
+
27
+ instance_eval(&block)
28
+
29
+ context "executing it twice" do
30
+ before do
31
+ future.to_a
32
+ end
33
+
34
+ specify do
35
+ calling_to_a.should exec(0).queries
36
+ end
37
+
38
+ instance_eval(&block)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ spec_relation "filters by published_at", -> { Post.where("published_at < ?", Time.new(2013, 1, 1)) } do
45
+ specify { future.to_a.should have(2).posts }
46
+ end
47
+
48
+ spec_relation "limits by 10", -> { Post.limit(10) } do
49
+ specify { future.to_a.should have(3).posts }
50
+ end
51
+
52
+ end
@@ -0,0 +1,4 @@
1
+ class Comment < ActiveRecord::Base
2
+ belongs_to :user
3
+ belongs_to :post
4
+ end
@@ -0,0 +1,3 @@
1
+ class Post < ActiveRecord::Base
2
+ default_scope { where("published_at is not null") }
3
+ end
data/spec/models/user.rb CHANGED
@@ -1,2 +1,3 @@
1
1
  class User < ActiveRecord::Base
2
+ has_many :comments
2
3
  end
data/spec/spec_helper.rb CHANGED
@@ -1,18 +1,47 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
1
4
  require 'activerecord-futures'
2
5
 
3
- config = {
4
- adapter: "future_enabled_mysql2",
5
- database: "test",
6
- username: "root",
7
- password: "root",
8
- database: "activerecord_futures_test",
9
- host: "localhost"
6
+ configs = {
7
+ future_enabled_mysql2: {
8
+ adapter: "future_enabled_mysql2",
9
+ database: "activerecord_futures_test",
10
+ username: "root",
11
+ encoding: "utf8"
12
+ },
13
+ future_enabled_postgresql: {
14
+ adapter: "future_enabled_postgresql",
15
+ database: "activerecord_futures_test",
16
+ username: "postgres"
17
+ },
18
+ postgresql: {
19
+ adapter: "postgresql",
20
+ database: "activerecord_futures_test",
21
+ username: "postgres"
22
+ },
23
+ mysql2: {
24
+ adapter: "mysql2",
25
+ database: "activerecord_futures_test",
26
+ username: "root",
27
+ encoding: "utf8"
28
+ }
10
29
  }
11
30
 
31
+ env_config = ENV['ADAPTER'].try(:to_sym)
32
+ config_key = configs.keys.include?(env_config) ? env_config : :future_enabled_mysql2
33
+ config = configs[config_key]
34
+ puts "Using #{config_key} configuration"
35
+
12
36
  ActiveRecord::Base.establish_connection(config)
37
+ supports_futures =
38
+ ActiveRecord::Base.connection.respond_to?(:supports_futures?) &&
39
+ ActiveRecord::Base.connection.supports_futures?
40
+
13
41
  require 'db/schema'
42
+ Dir['./spec/models/**/*.rb'].each { |f| require f }
14
43
 
15
- Dir[File.join(File.dirname(__FILE__), 'models/**/*')].each { |f| require f }
44
+ Dir["./spec/support/**/*.rb"].sort.each {|f| require f}
16
45
 
17
46
  require 'rspec-spies'
18
47
 
@@ -20,13 +49,20 @@ RSpec.configure do |config|
20
49
  config.treat_symbols_as_metadata_keys_with_true_values = true
21
50
  config.run_all_when_everything_filtered = true
22
51
  config.filter_run :focus
23
-
52
+ config.filter_run_excluding(supports_futures ? :not_supporting_adapter : :supporting_adapter)
24
53
  # Run specs in random order to surface order dependencies. If you find an
25
54
  # order dependency and want to debug it, you can fix the order by providing
26
55
  # the seed, which is printed after each run.
27
56
  # --seed 1234
28
57
  config.order = 'random'
29
58
 
59
+ config.around do |example|
60
+ ActiveRecord::Base.transaction do
61
+ example.run
62
+ raise ActiveRecord::Rollback
63
+ end
64
+ end
65
+
30
66
  config.after do
31
67
  ActiveRecord::Futures::Future.clear
32
68
  end
@@ -0,0 +1,66 @@
1
+ RSpec::Matchers.define :exec do |expected|
2
+
3
+ match do |block|
4
+ query_count(&block) == expected
5
+ end
6
+
7
+ chain :queries do
8
+ end
9
+
10
+ chain :query do
11
+ end
12
+
13
+ description do
14
+ queries = expected == 1 ? "query" : "queries"
15
+ "exec #{expected} #{queries}"
16
+ end
17
+
18
+ failure_message_for_should do |actual|
19
+ "Expected to execute #{expected} queries, executed #{@query_counter.query_count}: #{@query_counter.queries}"
20
+ end
21
+
22
+ failure_message_for_should_not do |actual|
23
+ "Expected to not execute #{expected} queries"
24
+ end
25
+
26
+ def query_count(&block)
27
+ @query_counter = QueryCounter.new
28
+ ActiveSupport::Notifications.subscribed(@query_counter.method(:call), 'sql.active_record', &block)
29
+ @query_counter.query_count
30
+ end
31
+ end
32
+
33
+ class QueryCounter
34
+ attr_accessor :query_count
35
+ attr_accessor :queries
36
+
37
+ IGNORED_SQL = [
38
+ /^PRAGMA (?!(table_info))/,
39
+ /^SELECT currval/,
40
+ /^SELECT CAST/,
41
+ /^SELECT @@IDENTITY/,
42
+ /^SELECT @@ROWCOUNT/,
43
+ /^SAVEPOINT/,
44
+ /^ROLLBACK TO SAVEPOINT/,
45
+ /^RELEASE SAVEPOINT/,
46
+ /^SHOW max_identifier_length/,
47
+ /SELECT attr.attname\n FROM pg_attribute attr/,
48
+ /SHOW/
49
+ ]
50
+
51
+ def initialize
52
+ self.query_count = 0
53
+ self.queries = []
54
+ end
55
+
56
+ def call(name, start, finish, message_id, values)
57
+ # FIXME: this seems bad. we should probably have a better way to indicate
58
+ # the query was cached
59
+ unless 'CACHE' == values[:name]
60
+ unless IGNORED_SQL.any? { |r| values[:sql] =~ r }
61
+ self.query_count += 1
62
+ self.queries << values[:sql]
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,23 @@
1
+ RSpec::Matchers.define :exec_query do |expected|
2
+
3
+ match do |block|
4
+ query(&block) == expected
5
+ end
6
+
7
+ failure_message_for_should do |actual|
8
+ "Expected to execute #{expected}, got #{@query}"
9
+ end
10
+
11
+ failure_message_for_should_not do |actual|
12
+ "Expected to not execute #{expected}, got #{actual}"
13
+ end
14
+
15
+ def query(&block)
16
+ query = lambda do |name, start, finish, message_id, values|
17
+ @query = values[:sql]
18
+ end
19
+ ActiveSupport::Notifications.subscribed(query, 'sql.active_record', &block)
20
+ @query
21
+ end
22
+
23
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-futures
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-12 00:00:00.000000000 Z
12
+ date: 2013-04-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
- version: 3.2.13
21
+ version: 3.2.11
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
- version: 3.2.13
29
+ version: 3.2.11
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: rspec
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -59,6 +59,38 @@ dependencies:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: mysql2
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 0.3.12.b1
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 0.3.12.b1
78
+ - !ruby/object:Gem::Dependency
79
+ name: pg
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
62
94
  description: Save unnecessary round trips to the database
63
95
  email:
64
96
  - leoasis@gmail.com
@@ -68,27 +100,41 @@ extra_rdoc_files: []
68
100
  files:
69
101
  - .gitignore
70
102
  - .rspec
103
+ - .travis.yml
71
104
  - Gemfile
72
105
  - LICENSE.txt
73
106
  - README.md
74
107
  - Rakefile
75
108
  - activerecord-futures.gemspec
109
+ - lib/active_record/connection_adapters/future_enabled.rb
76
110
  - lib/active_record/connection_adapters/future_enabled_mysql2_adapter.rb
111
+ - lib/active_record/connection_adapters/future_enabled_postgresql_adapter.rb
77
112
  - lib/active_record/futures.rb
78
113
  - lib/active_record/futures/delegation.rb
79
114
  - lib/active_record/futures/future.rb
80
115
  - lib/active_record/futures/future_calculation.rb
116
+ - lib/active_record/futures/future_calculation_array.rb
117
+ - lib/active_record/futures/future_calculation_value.rb
81
118
  - lib/active_record/futures/future_relation.rb
119
+ - lib/active_record/futures/proxy.rb
82
120
  - lib/active_record/futures/query_recording.rb
83
121
  - lib/activerecord-futures.rb
84
122
  - lib/activerecord-futures/version.rb
85
123
  - spec/active_record/futures/future_relation_spec.rb
86
124
  - spec/active_record/futures/future_spec.rb
125
+ - spec/active_record/futures/proxy_spec.rb
87
126
  - spec/db/schema.rb
127
+ - spec/in_action/combination_of_futures_spec.rb
128
+ - spec/in_action/future_count_execution_spec.rb
88
129
  - spec/in_action/future_fulfillment_spec.rb
89
- - spec/models/country.rb
130
+ - spec/in_action/future_pluck_execution_spec.rb
131
+ - spec/in_action/future_relation_execution_spec.rb
132
+ - spec/models/comment.rb
133
+ - spec/models/post.rb
90
134
  - spec/models/user.rb
91
135
  - spec/spec_helper.rb
136
+ - spec/support/matchers/exec.rb
137
+ - spec/support/matchers/exec_query.rb
92
138
  homepage: https://github.com/leoasis/activerecord-futures
93
139
  licenses: []
94
140
  post_install_message:
@@ -116,9 +162,17 @@ summary: Fetch all queries at once from the database and save round trips.
116
162
  test_files:
117
163
  - spec/active_record/futures/future_relation_spec.rb
118
164
  - spec/active_record/futures/future_spec.rb
165
+ - spec/active_record/futures/proxy_spec.rb
119
166
  - spec/db/schema.rb
167
+ - spec/in_action/combination_of_futures_spec.rb
168
+ - spec/in_action/future_count_execution_spec.rb
120
169
  - spec/in_action/future_fulfillment_spec.rb
121
- - spec/models/country.rb
170
+ - spec/in_action/future_pluck_execution_spec.rb
171
+ - spec/in_action/future_relation_execution_spec.rb
172
+ - spec/models/comment.rb
173
+ - spec/models/post.rb
122
174
  - spec/models/user.rb
123
175
  - spec/spec_helper.rb
176
+ - spec/support/matchers/exec.rb
177
+ - spec/support/matchers/exec_query.rb
124
178
  has_rdoc: