n_plus_one_control 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop.yml +5 -2
- data/README.md +50 -0
- data/lib/n_plus_one_control/minitest.rb +8 -1
- data/lib/n_plus_one_control/rspec/context.rb +5 -0
- data/lib/n_plus_one_control/rspec/dsl.rb +10 -1
- data/lib/n_plus_one_control/rspec/matcher.rb +8 -0
- data/lib/n_plus_one_control/version.rb +1 -1
- data/spec/n_plus_one_control/rspec_spec.rb +29 -0
- data/spec/spec_helper.rb +1 -1
- data/tests/test_helper.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 292888473b7ae70cfdc9c8e9324170dfaf1d7048fc27a8c33a3ef09ee7b3fc3a
|
4
|
+
data.tar.gz: 99bf992bbe635524c8eaea3f6536f4813e05b4420bc1f26ecea8ab26fecfc0f1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd45056252d7ae15236e8ae5d5880c0bda50f741ac3cf2079a42f5870cf378fb52d791b444c57c25218428d52e046841a5a762708a18ad868103d9e17f0d30f1
|
7
|
+
data.tar.gz: b17d7be7d184ffa37ce4f6162b694928eb96968cc5f4e05e42971d68954709ed33c43b36c5549d49823ac2578103d9420cf713dd4f6cbc12880e885203a87bb4
|
data/.rubocop.yml
CHANGED
@@ -17,7 +17,10 @@ AllCops:
|
|
17
17
|
Rails:
|
18
18
|
Enabled: false
|
19
19
|
|
20
|
-
|
20
|
+
Naming/UncommunicativeMethodParamName:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Naming/AccessorMethodName:
|
21
24
|
Enabled: false
|
22
25
|
|
23
26
|
Style/TrivialAccessors:
|
@@ -37,7 +40,7 @@ Style/RegexpLiteral:
|
|
37
40
|
Style/Lambda:
|
38
41
|
Enabled: false
|
39
42
|
|
40
|
-
|
43
|
+
Layout/SpaceInsideStringInterpolation:
|
41
44
|
EnforcedStyle: no_space
|
42
45
|
|
43
46
|
Style/ClassAndModuleChildren:
|
data/README.md
CHANGED
@@ -147,6 +147,56 @@ def test_no_n_plus_one_error
|
|
147
147
|
end
|
148
148
|
```
|
149
149
|
|
150
|
+
### With caching
|
151
|
+
|
152
|
+
If you use caching you can face the problem when first request performs more DB queries than others. The solution is:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
# RSpec
|
156
|
+
|
157
|
+
context "N + 1", :n_plus_one do
|
158
|
+
populate { |n| create_list :post, n }
|
159
|
+
|
160
|
+
warmup { get :index } # cache something must be cached
|
161
|
+
|
162
|
+
specify do
|
163
|
+
expect { get :index }.to perform_constant_number_of_queries
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Minitest
|
168
|
+
|
169
|
+
def populate(n)
|
170
|
+
create_list(:post, n)
|
171
|
+
end
|
172
|
+
|
173
|
+
def warmup
|
174
|
+
get :index
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_no_n_plus_one_error
|
178
|
+
assert_perform_constant_number_of_queries do
|
179
|
+
get :index
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# or with params
|
184
|
+
|
185
|
+
def test_no_n_plus_one
|
186
|
+
populate = ->(n) { create_list(:post, n) }
|
187
|
+
warmup = -> { get :index }
|
188
|
+
|
189
|
+
assert_perform_constant_number_of_queries population: populate, warmup: warmup do
|
190
|
+
get :index
|
191
|
+
end
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
If your `warmup` and testing procs are identical, you can use:
|
196
|
+
```ruby
|
197
|
+
expext { get :index }.to perform_constant_number_of_queries.with_warming_up # RSpec only
|
198
|
+
```
|
199
|
+
|
150
200
|
### Configuration
|
151
201
|
|
152
202
|
There are some global configuration parameters (and their corresponding defaults):
|
@@ -5,14 +5,21 @@ require "n_plus_one_control"
|
|
5
5
|
module NPlusOneControl
|
6
6
|
# Minitest assertions
|
7
7
|
module MinitestHelper
|
8
|
+
def warming_up(warmup)
|
9
|
+
(warmup || methods.include?(:warmup) ? method(:warmup) : nil)&.call
|
10
|
+
end
|
11
|
+
|
8
12
|
def assert_perform_constant_number_of_queries(
|
9
13
|
populate: nil,
|
10
14
|
matching: nil,
|
11
|
-
scale_factors: nil
|
15
|
+
scale_factors: nil,
|
16
|
+
warmup: nil
|
12
17
|
)
|
13
18
|
|
14
19
|
raise ArgumentError, "Block is required" unless block_given?
|
15
20
|
|
21
|
+
warming_up warmup
|
22
|
+
|
16
23
|
queries = NPlusOneControl::Executor.call(
|
17
24
|
population: populate || method(:populate),
|
18
25
|
matching: matching || /^SELECT/i,
|
@@ -2,8 +2,17 @@
|
|
2
2
|
|
3
3
|
module NPlusOneControl
|
4
4
|
module RSpec
|
5
|
-
# Extends RSpec ExampleGroup with populate
|
5
|
+
# Extends RSpec ExampleGroup with populate & warmup methods
|
6
6
|
module DSL
|
7
|
+
# Setup warmup block, wich will run before matching
|
8
|
+
# for example, if using cache, then later queries
|
9
|
+
# will perform less DB queries than first
|
10
|
+
def warmup
|
11
|
+
return @warmup unless block_given?
|
12
|
+
|
13
|
+
@warmup = Proc.new
|
14
|
+
end
|
15
|
+
|
7
16
|
# Setup populate callback, which is used
|
8
17
|
# to prepare data for each run.
|
9
18
|
def populate
|
@@ -12,6 +12,10 @@
|
|
12
12
|
@pattern = pattern
|
13
13
|
end
|
14
14
|
|
15
|
+
chain :with_warming_up do
|
16
|
+
@warmup = true
|
17
|
+
end
|
18
|
+
|
15
19
|
match do |actual, *_args|
|
16
20
|
raise ArgumentError, "Block is required" unless actual.is_a? Proc
|
17
21
|
|
@@ -19,6 +23,9 @@
|
|
19
23
|
@matcher_execution_context.respond_to?(:n_plus_one_populate)
|
20
24
|
|
21
25
|
populate = @matcher_execution_context.n_plus_one_populate
|
26
|
+
warmup = @warmup ? actual : @matcher_execution_context.n_plus_one_warmup
|
27
|
+
|
28
|
+
warmup.call if warmup.present?
|
22
29
|
|
23
30
|
# by default we're looking for select queries
|
24
31
|
pattern = @pattern || /^SELECT/i
|
@@ -41,3 +48,4 @@
|
|
41
48
|
|
42
49
|
failure_message { |_actual| NPlusOneControl.failure_message(@queries) }
|
43
50
|
end
|
51
|
+
# rubocop:enable Metrics/BlockLength
|
@@ -81,4 +81,33 @@ describe NPlusOneControl::RSpec do
|
|
81
81
|
.to perform_constant_number_of_queries.matching(/posts/)
|
82
82
|
end
|
83
83
|
end
|
84
|
+
|
85
|
+
context 'with warming up', :n_plus_one do
|
86
|
+
let(:cache) { double "cache" }
|
87
|
+
|
88
|
+
before do
|
89
|
+
allow(cache).to receive(:setup).and_return(:result)
|
90
|
+
allow(NPlusOneControl::Executor).to receive(:call) { raise StandardError }
|
91
|
+
end
|
92
|
+
|
93
|
+
populate { |n| create_list(:post, n) }
|
94
|
+
|
95
|
+
warmup { cache.setup }
|
96
|
+
|
97
|
+
it "runs warmup before calling Executor" do
|
98
|
+
expect(cache).to receive(:setup)
|
99
|
+
expect do
|
100
|
+
expect { Post.find_each(&:id) }.to perform_constant_number_of_queries
|
101
|
+
end.to raise_error StandardError
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'with_warming_up', :n_plus_one do
|
106
|
+
populate { |n| create_list(:post, n) }
|
107
|
+
|
108
|
+
it "runs actual one more time" do
|
109
|
+
expect(Post).to receive(:all).exactly(3).times
|
110
|
+
expect { Post.all }.to perform_constant_number_of_queries.with_warming_up
|
111
|
+
end
|
112
|
+
end
|
84
113
|
end
|
data/spec/spec_helper.rb
CHANGED
data/tests/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: n_plus_one_control
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- palkan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-06-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -196,7 +196,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
196
196
|
version: '0'
|
197
197
|
requirements: []
|
198
198
|
rubyforge_project:
|
199
|
-
rubygems_version: 2.6
|
199
|
+
rubygems_version: 2.7.6
|
200
200
|
signing_key:
|
201
201
|
specification_version: 4
|
202
202
|
summary: RSpec and Minitest matchers to prevent N+1 queries problem
|