n_plus_one_control 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -1
- data/README.md +29 -2
- data/lib/n_plus_one_control.rb +8 -3
- data/lib/n_plus_one_control/minitest.rb +34 -1
- data/lib/n_plus_one_control/rspec.rb +2 -1
- data/lib/n_plus_one_control/rspec/{matcher.rb → matchers/perform_constant_number_of_queries.rb} +1 -1
- data/lib/n_plus_one_control/rspec/matchers/perform_linear_number_of_queries.rb +53 -0
- data/lib/n_plus_one_control/version.rb +1 -1
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48ad0338e6227c2b71a09254f3613db415d64fe5b40ee7df55ee1daddc46c0f5
|
4
|
+
data.tar.gz: c55895975614627ea2308dde866079bad496f5ddef81f295267f5b0d8b2670ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4f6b44d7ecccd6e3f53d65e4df916982b144b8381049ebec5659cdf5792cc9c1d1c7fdee22cb8610df155020220286cc2daf78a0dbe8b76fe6bceb778446c81
|
7
|
+
data.tar.gz: 63ac722a3b82c6c3263a79f0065f554352bf4ce2043bdec98f37139f8b21f441e9327bc575ff6e3bdde05e8eac95ce636615e74ed4e3415a0ae28312da43d2a7
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
##
|
1
|
+
## 0.6.0 (2020-11-27)
|
2
|
+
|
3
|
+
- Fix table stats summary when queries use backticks to surround table names ([@andrewhampton][])
|
4
|
+
- Add support to test for linear query. ([@caalberts][])
|
2
5
|
|
3
6
|
## 0.5.0 (2020-09-07)
|
4
7
|
|
@@ -26,3 +29,5 @@ Could be specified via `NPLUSONE_BACKTRACE` env var.
|
|
26
29
|
|
27
30
|
[@Earendil95]: https://github.com/Earendil95
|
28
31
|
[@palkan]: https://github.com/palkan
|
32
|
+
[@caalberts]: https://github.com/caalberts
|
33
|
+
[@andrewhampton]: https://github.com/andrewhampton
|
data/README.md
CHANGED
@@ -85,10 +85,10 @@ Availables modifiers:
|
|
85
85
|
```ruby
|
86
86
|
# You can specify the RegExp to filter queries.
|
87
87
|
# By default, it only considers SELECT queries.
|
88
|
-
expect {
|
88
|
+
expect { get :index }.to perform_constant_number_of_queries.matching(/INSERT/)
|
89
89
|
|
90
90
|
# You can also provide custom scale factors
|
91
|
-
expect {
|
91
|
+
expect { get :index }.to perform_constant_number_of_queries.with_scale_factors(10, 100)
|
92
92
|
```
|
93
93
|
|
94
94
|
#### Using scale factor in spec
|
@@ -111,6 +111,21 @@ context "N+1", :n_plus_one do
|
|
111
111
|
end
|
112
112
|
```
|
113
113
|
|
114
|
+
#### Other available matchers
|
115
|
+
|
116
|
+
`perform_linear_number_of_queries(slope: 1)` allows you to test that a query generates linear number of queries with the given slope.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
context "when has linear query", :n_plus_one do
|
120
|
+
populate { |n| create_list(:post, n) }
|
121
|
+
|
122
|
+
specify do
|
123
|
+
expect { Post.find_each { |p| p.user.name } }
|
124
|
+
.to perform_linear_number_of_queries(slope: 1)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
114
129
|
### Minitest
|
115
130
|
|
116
131
|
First, add NPlusOneControl to your `test_helper.rb`:
|
@@ -132,6 +147,18 @@ def test_no_n_plus_one_error
|
|
132
147
|
end
|
133
148
|
```
|
134
149
|
|
150
|
+
You can also use `assert_perform_linear_number_of_queries` to test for linear queries:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
def test_no_n_plus_one_error
|
154
|
+
populate = ->(n) { create_list(:post, n) }
|
155
|
+
|
156
|
+
assert_perform_linear_number_of_queries(slope: 1, populate: populate) do
|
157
|
+
Post.find_each { |p| p.user.name }
|
158
|
+
end
|
159
|
+
end
|
160
|
+
```
|
161
|
+
|
135
162
|
You can also specify custom scale factors or filter patterns:
|
136
163
|
|
137
164
|
```ruby
|
data/lib/n_plus_one_control.rb
CHANGED
@@ -6,7 +6,7 @@ require "n_plus_one_control/executor"
|
|
6
6
|
# RSpec and Minitest matchers to prevent N+1 queries problem.
|
7
7
|
module NPlusOneControl
|
8
8
|
# Used to extract a table name from a query
|
9
|
-
EXTRACT_TABLE_RXP = /(insert into|update|delete from|from) ['"](\S+)['"]/i.freeze
|
9
|
+
EXTRACT_TABLE_RXP = /(insert into|update|delete from|from) ['"`](\S+)['"`]/i.freeze
|
10
10
|
|
11
11
|
# Used to convert a query part extracted by the regexp above to the corresponding
|
12
12
|
# human-friendly type
|
@@ -23,8 +23,13 @@ module NPlusOneControl
|
|
23
23
|
|
24
24
|
attr_reader :default_matching
|
25
25
|
|
26
|
-
|
27
|
-
|
26
|
+
FAILURE_MESSAGES = {
|
27
|
+
constant_queries: "Expected to make the same number of queries",
|
28
|
+
linear_queries: "Expected to make linear number of queries"
|
29
|
+
}
|
30
|
+
|
31
|
+
def failure_message(type, queries) # rubocop:disable Metrics/MethodLength
|
32
|
+
msg = ["#{FAILURE_MESSAGES[type]}, but got:\n"]
|
28
33
|
queries.each do |(scale, data)|
|
29
34
|
msg << " #{data.size} for N=#{scale}\n"
|
30
35
|
end
|
@@ -26,7 +26,30 @@ module NPlusOneControl
|
|
26
26
|
|
27
27
|
counts = queries.map(&:last).map(&:size)
|
28
28
|
|
29
|
-
assert counts.max == counts.min, NPlusOneControl.failure_message(queries)
|
29
|
+
assert counts.max == counts.min, NPlusOneControl.failure_message(:constant_queries, queries)
|
30
|
+
end
|
31
|
+
|
32
|
+
def assert_perform_linear_number_of_queries(
|
33
|
+
slope: 1,
|
34
|
+
populate: nil,
|
35
|
+
matching: nil,
|
36
|
+
scale_factors: nil,
|
37
|
+
warmup: nil
|
38
|
+
)
|
39
|
+
|
40
|
+
raise ArgumentError, "Block is required" unless block_given?
|
41
|
+
|
42
|
+
warming_up warmup
|
43
|
+
|
44
|
+
@executor = NPlusOneControl::Executor.new(
|
45
|
+
population: populate || population_method,
|
46
|
+
matching: matching || NPlusOneControl.default_matching,
|
47
|
+
scale_factors: scale_factors || NPlusOneControl.default_scale_factors
|
48
|
+
)
|
49
|
+
|
50
|
+
queries = @executor.call { yield }
|
51
|
+
|
52
|
+
assert linear?(queries, slope: slope), NPlusOneControl.failure_message(:linear_queries, queries)
|
30
53
|
end
|
31
54
|
|
32
55
|
def current_scale
|
@@ -42,6 +65,16 @@ module NPlusOneControl
|
|
42
65
|
def population_method
|
43
66
|
methods.include?(:populate) ? method(:populate) : nil
|
44
67
|
end
|
68
|
+
|
69
|
+
def linear?(queries, slope:)
|
70
|
+
queries.each_cons(2).all? do |pair|
|
71
|
+
scales = pair.map(&:first)
|
72
|
+
query_lists = pair.map(&:last)
|
73
|
+
|
74
|
+
actual_slope = (query_lists[1].size - query_lists[0].size) / (scales[1] - scales[0])
|
75
|
+
actual_slope <= slope
|
76
|
+
end
|
77
|
+
end
|
45
78
|
end
|
46
79
|
end
|
47
80
|
|
@@ -4,7 +4,8 @@ gem "rspec-core", ">= 3.5"
|
|
4
4
|
|
5
5
|
require "n_plus_one_control"
|
6
6
|
require "n_plus_one_control/rspec/dsl"
|
7
|
-
require "n_plus_one_control/rspec/
|
7
|
+
require "n_plus_one_control/rspec/matchers/perform_constant_number_of_queries"
|
8
|
+
require "n_plus_one_control/rspec/matchers/perform_linear_number_of_queries"
|
8
9
|
require "n_plus_one_control/rspec/context"
|
9
10
|
|
10
11
|
module NPlusOneControl
|
data/lib/n_plus_one_control/rspec/{matcher.rb → matchers/perform_constant_number_of_queries.rb}
RENAMED
@@ -46,6 +46,6 @@
|
|
46
46
|
raise "This matcher doesn't support negation"
|
47
47
|
end
|
48
48
|
|
49
|
-
failure_message { |_actual| NPlusOneControl.failure_message(@queries) }
|
49
|
+
failure_message { |_actual| NPlusOneControl.failure_message(:constant_queries, @queries) }
|
50
50
|
end
|
51
51
|
# rubocop:enable Metrics/BlockLength
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Metrics/BlockLength
|
4
|
+
::RSpec::Matchers.define :perform_linear_number_of_queries do |slope: 1|
|
5
|
+
supports_block_expectations
|
6
|
+
|
7
|
+
chain :with_scale_factors do |*factors|
|
8
|
+
@factors = factors
|
9
|
+
end
|
10
|
+
|
11
|
+
chain :matching do |pattern|
|
12
|
+
@pattern = pattern
|
13
|
+
end
|
14
|
+
|
15
|
+
chain :with_warming_up do
|
16
|
+
@warmup = true
|
17
|
+
end
|
18
|
+
|
19
|
+
match do |actual, *_args|
|
20
|
+
raise ArgumentError, "Block is required" unless actual.is_a? Proc
|
21
|
+
|
22
|
+
raise "Missing tag :n_plus_one" unless
|
23
|
+
@matcher_execution_context.respond_to?(:n_plus_one_populate)
|
24
|
+
|
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?
|
29
|
+
|
30
|
+
@matcher_execution_context.executor = NPlusOneControl::Executor.new(
|
31
|
+
population: populate,
|
32
|
+
matching: nil,
|
33
|
+
scale_factors: @factors
|
34
|
+
)
|
35
|
+
|
36
|
+
@queries = @matcher_execution_context.executor.call(&actual)
|
37
|
+
|
38
|
+
@queries.each_cons(2).all? do |pair|
|
39
|
+
scales = pair.map(&:first)
|
40
|
+
query_lists = pair.map(&:last)
|
41
|
+
|
42
|
+
actual_slope = (query_lists[1].size - query_lists[0].size) / (scales[1] - scales[0])
|
43
|
+
actual_slope <= slope
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
match_when_negated do |_actual|
|
48
|
+
raise "This matcher doesn't support negation"
|
49
|
+
end
|
50
|
+
|
51
|
+
failure_message { |_actual| NPlusOneControl.failure_message(:linear_queries, @queries) }
|
52
|
+
end
|
53
|
+
# rubocop:enable Metrics/BlockLength
|
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.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- palkan
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -100,7 +100,8 @@ files:
|
|
100
100
|
- lib/n_plus_one_control/rspec.rb
|
101
101
|
- lib/n_plus_one_control/rspec/context.rb
|
102
102
|
- lib/n_plus_one_control/rspec/dsl.rb
|
103
|
-
- lib/n_plus_one_control/rspec/
|
103
|
+
- lib/n_plus_one_control/rspec/matchers/perform_constant_number_of_queries.rb
|
104
|
+
- lib/n_plus_one_control/rspec/matchers/perform_linear_number_of_queries.rb
|
104
105
|
- lib/n_plus_one_control/version.rb
|
105
106
|
homepage: http://github.com/palkan/n_plus_one_control
|
106
107
|
licenses:
|
@@ -111,7 +112,7 @@ metadata:
|
|
111
112
|
documentation_uri: http://github.com/palkan/n_plus_one_control
|
112
113
|
homepage_uri: http://github.com/palkan/n_plus_one_control
|
113
114
|
source_code_uri: http://github.com/palkan/n_plus_one_control
|
114
|
-
post_install_message:
|
115
|
+
post_install_message:
|
115
116
|
rdoc_options: []
|
116
117
|
require_paths:
|
117
118
|
- lib
|
@@ -126,8 +127,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
127
|
- !ruby/object:Gem::Version
|
127
128
|
version: '0'
|
128
129
|
requirements: []
|
129
|
-
rubygems_version: 3.0.
|
130
|
-
signing_key:
|
130
|
+
rubygems_version: 3.0.3
|
131
|
+
signing_key:
|
131
132
|
specification_version: 4
|
132
133
|
summary: RSpec and Minitest matchers to prevent N+1 queries problem
|
133
134
|
test_files: []
|