activerecord-futures 0.0.1 → 0.1.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 (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: