n_plus_one_control 0.6.2 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/LICENSE.txt +1 -1
- data/README.md +39 -5
- data/lib/n_plus_one_control/minitest.rb +10 -3
- data/lib/n_plus_one_control/rspec/dsl.rb +1 -1
- data/lib/n_plus_one_control/rspec/matchers/perform_constant_number_of_queries.rb +11 -5
- data/lib/n_plus_one_control/rspec/matchers/perform_linear_number_of_queries.rb +0 -2
- data/lib/n_plus_one_control/version.rb +1 -1
- data/lib/n_plus_one_control.rb +5 -3
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e2527bc8ec3ef67b4266b9a7221251d36f29f82697823eb6bba1e301031cd6f
|
4
|
+
data.tar.gz: b7117a64e62367c75ddd610c3c520ea7472f3d8f80d6e5f228c4642ed3ef2962
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 29583c392acbdc865b6a01cb1b9e132fe128bf3f9c3b3ced9087b6a58611577315dd4f86fe92ca90f469473ac080fba63f98a95bb771169a34f7d3d39c36f395
|
7
|
+
data.tar.gz: 752b24bd5bf12d77a89c6fcabfc782e6a0757c98265e519fe1becd1929b058369dd2d545ab3eb6f6c9c4f3eac79e5e099c59c6735247fecf34cde20a6f43cf78
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,30 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.7.1 (2023-02-17)
|
6
|
+
|
7
|
+
- Recognize private `#populate` and `#warmup` methods in Minitest classes. ([@palkan][])
|
8
|
+
|
9
|
+
## 0.7.0 (2023-02-17)
|
10
|
+
|
11
|
+
- Added ability to specify the exact number of expected queries when using constant matchers. ([@akostadinov][], [@palkan][])
|
12
|
+
|
13
|
+
For RSpec, you can add the `.exactly` modifier:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
expect { get :index }.to perform_constant_number_of_queries.exactly(1)
|
17
|
+
```
|
18
|
+
|
19
|
+
For Minitest, you can provide the expected number of queries as the first argument:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
assert_perform_constant_number_of_queries(0, **options) do
|
23
|
+
get :index
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
- **Require Ruby 2.7+**.
|
28
|
+
|
5
29
|
## 0.6.2 (2021-10-26)
|
6
30
|
|
7
31
|
- Fix .ignore setting (.ignore setting was ignored by the Collector ;-))
|
@@ -44,3 +68,4 @@ Could be specified via `NPLUSONE_BACKTRACE` env var.
|
|
44
68
|
[@palkan]: https://github.com/palkan
|
45
69
|
[@caalberts]: https://github.com/caalberts
|
46
70
|
[@andrewhampton]: https://github.com/andrewhampton
|
71
|
+
[@akostadinov]: https://github.com/akostadinov
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2017-
|
3
|
+
Copyright (c) 2017-2023 Vladimir Dementyev
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -17,6 +17,8 @@ NPlusOneControl works differently. It evaluates the code under consideration sev
|
|
17
17
|
|
18
18
|
So, it's for _performance_ testing and not _feature_ testing.
|
19
19
|
|
20
|
+
> Read also ["Squash N+1 queries early with n_plus_one_control test matchers for Ruby and Rails"](https://evilmartians.com/chronicles/squash-n-plus-one-queries-early-with-n-plus-one-control-test-matchers-for-ruby-and-rails).
|
21
|
+
|
20
22
|
### Why not just use [`bullet`](https://github.com/flyerhzm/bullet)?
|
21
23
|
|
22
24
|
Of course, it's possible to use Bullet in tests (see more [here](https://evilmartians.com/chronicles/fighting-the-hydra-of-n-plus-one-queries)), but it's not a _silver bullet_: there can be both false positives and true negatives.
|
@@ -72,12 +74,33 @@ end
|
|
72
74
|
|
73
75
|
```ruby
|
74
76
|
# BAD – won't work!
|
75
|
-
|
76
77
|
subject { get :index }
|
77
78
|
|
78
79
|
specify do
|
79
80
|
expect { subject }.to perform_constant_number_of_queries
|
80
81
|
end
|
82
|
+
|
83
|
+
# GOOD
|
84
|
+
specify do
|
85
|
+
expect { get :index }.to perform_constant_number_of_queries
|
86
|
+
end
|
87
|
+
|
88
|
+
# BAD — the `page` record would be removed from the database
|
89
|
+
# but still present in RSpec (due to `let`'s memoization)
|
90
|
+
let(:page) { create(:page) }
|
91
|
+
|
92
|
+
populate { |n| create_list(:comment, n, page: page) }
|
93
|
+
|
94
|
+
specify do
|
95
|
+
expect { get :show, params: {id: page.id} }.to perform_constant_number_of_queries
|
96
|
+
end
|
97
|
+
|
98
|
+
# GOOD
|
99
|
+
# Ensure the record is created before `populate`
|
100
|
+
let!(:page) { create(:page) }
|
101
|
+
|
102
|
+
populate { |n| create_list(:comment, n, page: page) }
|
103
|
+
# ...
|
81
104
|
```
|
82
105
|
|
83
106
|
Availables modifiers:
|
@@ -89,6 +112,9 @@ expect { get :index }.to perform_constant_number_of_queries.matching(/INSERT/)
|
|
89
112
|
|
90
113
|
# You can also provide custom scale factors
|
91
114
|
expect { get :index }.to perform_constant_number_of_queries.with_scale_factors(10, 100)
|
115
|
+
|
116
|
+
# You can specify the exact number of expected queries
|
117
|
+
expect { get :index }.to perform_constant_number_of_queries.exactly(1)
|
92
118
|
```
|
93
119
|
|
94
120
|
#### Using scale factor in spec
|
@@ -166,7 +192,7 @@ You can also use `assert_perform_linear_number_of_queries` to test for linear qu
|
|
166
192
|
```ruby
|
167
193
|
def test_no_n_plus_one_error
|
168
194
|
populate = ->(n) { create_list(:post, n) }
|
169
|
-
|
195
|
+
|
170
196
|
assert_perform_linear_number_of_queries(slope: 1, populate: populate) do
|
171
197
|
Post.find_each { |p| p.user.name }
|
172
198
|
end
|
@@ -191,6 +217,14 @@ assert_perform_constant_number_of_queries(
|
|
191
217
|
end
|
192
218
|
```
|
193
219
|
|
220
|
+
For the constant matcher, you can also specify the expected number of queries as the first argument:
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
assert_perform_constant_number_of_queries(2, populate: populate) do
|
224
|
+
get :index
|
225
|
+
end
|
226
|
+
```
|
227
|
+
|
194
228
|
It's possible to specify a filter via `NPLUSONE_FILTER` env var, e.g.:
|
195
229
|
|
196
230
|
```ruby
|
@@ -270,7 +304,7 @@ end
|
|
270
304
|
If your `warmup` and testing procs are identical, you can use:
|
271
305
|
|
272
306
|
```ruby
|
273
|
-
|
307
|
+
expect { get :index }.to perform_constant_number_of_queries.with_warming_up # RSpec only
|
274
308
|
```
|
275
309
|
|
276
310
|
### Configuration
|
@@ -302,7 +336,7 @@ NPlusOneControl.ignore = /^(BEGIN|COMMIT|SAVEPOINT|RELEASE)/
|
|
302
336
|
# but can also track rom-rb events ('sql.rom') as well.
|
303
337
|
NPlusOneControl.event = "sql.active_record"
|
304
338
|
|
305
|
-
# configure transactional
|
339
|
+
# configure transactional behaviour for populate method
|
306
340
|
# in case of use multiple database connections
|
307
341
|
NPlusOneControl::Executor.tap do |executor|
|
308
342
|
connections = ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
|
@@ -326,7 +360,7 @@ NPlusOneControl.backtrace_cleaner = ->(locations_array) { do_some_filtering(loca
|
|
326
360
|
NPlusOneControl.backtrace_length = 1
|
327
361
|
|
328
362
|
# Sometime queries could be too large to provide any meaningful insight.
|
329
|
-
# You can configure an output length limit for quries in verbose mode by setting the
|
363
|
+
# You can configure an output length limit for quries in verbose mode by setting the following option
|
330
364
|
# NOTE: It could be specified via NPLUSONE_TRUNCATE env var
|
331
365
|
NPlusOneControl.truncate_query_size = 100
|
332
366
|
```
|
@@ -6,6 +6,7 @@ module NPlusOneControl
|
|
6
6
|
# Minitest assertions
|
7
7
|
module MinitestHelper
|
8
8
|
def assert_perform_constant_number_of_queries(
|
9
|
+
exact = nil,
|
9
10
|
populate: nil,
|
10
11
|
matching: nil,
|
11
12
|
scale_factors: nil,
|
@@ -26,7 +27,11 @@ module NPlusOneControl
|
|
26
27
|
|
27
28
|
counts = queries.map(&:last).map(&:size)
|
28
29
|
|
29
|
-
|
30
|
+
if exact
|
31
|
+
assert counts.all? { _1 == exact }, NPlusOneControl.failure_message(:number_of_queries, queries)
|
32
|
+
else
|
33
|
+
assert counts.max == counts.min, NPlusOneControl.failure_message(:constant_queries, queries)
|
34
|
+
end
|
30
35
|
end
|
31
36
|
|
32
37
|
def assert_perform_linear_number_of_queries(
|
@@ -59,11 +64,13 @@ module NPlusOneControl
|
|
59
64
|
private
|
60
65
|
|
61
66
|
def warming_up(warmup)
|
62
|
-
|
67
|
+
own_methods = methods(false) + private_methods(false)
|
68
|
+
(warmup || (own_methods.include?(:warmup) ? method(:warmup) : nil))&.call
|
63
69
|
end
|
64
70
|
|
65
71
|
def population_method
|
66
|
-
methods
|
72
|
+
own_methods = methods(false) + private_methods(false)
|
73
|
+
own_methods.include?(:populate) ? method(:populate) : nil
|
67
74
|
end
|
68
75
|
|
69
76
|
def linear?(queries, slope:)
|
@@ -6,7 +6,7 @@ module NPlusOneControl
|
|
6
6
|
module DSL
|
7
7
|
# Extends RSpec ExampleGroup with populate & warmup methods
|
8
8
|
module ClassMethods
|
9
|
-
# Setup warmup block,
|
9
|
+
# Setup warmup block, which will run before matching
|
10
10
|
# for example, if using cache, then later queries
|
11
11
|
# will perform less DB queries than first
|
12
12
|
def warmup(&block)
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# rubocop:disable Metrics/BlockLength
|
4
3
|
::RSpec::Matchers.define :perform_constant_number_of_queries do
|
5
4
|
supports_block_expectations
|
6
5
|
|
@@ -12,6 +11,10 @@
|
|
12
11
|
@pattern = pattern
|
13
12
|
end
|
14
13
|
|
14
|
+
chain :exactly do |pattern|
|
15
|
+
@exactly = pattern
|
16
|
+
end
|
17
|
+
|
15
18
|
chain :with_warming_up do
|
16
19
|
@warmup = true
|
17
20
|
end
|
@@ -32,20 +35,23 @@
|
|
32
35
|
@matcher_execution_context.executor = NPlusOneControl::Executor.new(
|
33
36
|
population: populate,
|
34
37
|
matching: pattern,
|
35
|
-
scale_factors: @factors
|
38
|
+
scale_factors: @exactly ? [1] : @factors
|
36
39
|
)
|
37
40
|
|
38
41
|
@queries = @matcher_execution_context.executor.call(&actual)
|
39
42
|
|
40
43
|
counts = @queries.map(&:last).map(&:size)
|
41
44
|
|
42
|
-
|
45
|
+
if @exactly
|
46
|
+
counts.all? { _1 == @exactly }
|
47
|
+
else
|
48
|
+
counts.max == counts.min
|
49
|
+
end
|
43
50
|
end
|
44
51
|
|
45
52
|
match_when_negated do |_actual|
|
46
53
|
raise "This matcher doesn't support negation"
|
47
54
|
end
|
48
55
|
|
49
|
-
failure_message { |_actual| NPlusOneControl.failure_message(:constant_queries, @queries) }
|
56
|
+
failure_message { |_actual| NPlusOneControl.failure_message(@exactly ? :number_of_queries : :constant_queries, @queries) }
|
50
57
|
end
|
51
|
-
# rubocop:enable Metrics/BlockLength
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# rubocop:disable Metrics/BlockLength
|
4
3
|
::RSpec::Matchers.define :perform_linear_number_of_queries do |slope: 1|
|
5
4
|
supports_block_expectations
|
6
5
|
|
@@ -50,4 +49,3 @@
|
|
50
49
|
|
51
50
|
failure_message { |_actual| NPlusOneControl.failure_message(:linear_queries, @queries) }
|
52
51
|
end
|
53
|
-
# rubocop:enable Metrics/BlockLength
|
data/lib/n_plus_one_control.rb
CHANGED
@@ -25,7 +25,8 @@ module NPlusOneControl
|
|
25
25
|
|
26
26
|
FAILURE_MESSAGES = {
|
27
27
|
constant_queries: "Expected to make the same number of queries",
|
28
|
-
linear_queries: "Expected to make linear number of queries"
|
28
|
+
linear_queries: "Expected to make linear number of queries",
|
29
|
+
number_of_queries: "Expected to make the specified number of queries"
|
29
30
|
}
|
30
31
|
|
31
32
|
def failure_message(type, queries) # rubocop:disable Metrics/MethodLength
|
@@ -59,9 +60,10 @@ module NPlusOneControl
|
|
59
60
|
end
|
60
61
|
|
61
62
|
before.keys.each do |k|
|
62
|
-
next if before[k] == after
|
63
|
+
next if before[k] == after&.fetch(k, nil)
|
63
64
|
|
64
|
-
|
65
|
+
after_value = after ? " != #{after[k]}" : ""
|
66
|
+
msg << "#{k}: #{before[k]}#{after_value}\n"
|
65
67
|
end
|
66
68
|
|
67
69
|
msg
|
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.7.1
|
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: 2023-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -67,19 +67,19 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '5.9'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: factory_bot
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: '6.0'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
82
|
+
version: '6.0'
|
83
83
|
description: "\n RSpec and Minitest matchers to prevent N+1 queries problem.\n\n
|
84
84
|
\ Evaluates code under consideration several times with different scale factors\n
|
85
85
|
\ to make sure that the number of DB queries behaves as expected (i.e. O(1) instead
|
@@ -103,15 +103,15 @@ files:
|
|
103
103
|
- lib/n_plus_one_control/rspec/matchers/perform_constant_number_of_queries.rb
|
104
104
|
- lib/n_plus_one_control/rspec/matchers/perform_linear_number_of_queries.rb
|
105
105
|
- lib/n_plus_one_control/version.rb
|
106
|
-
homepage:
|
106
|
+
homepage: https://github.com/palkan/n_plus_one_control
|
107
107
|
licenses:
|
108
108
|
- MIT
|
109
109
|
metadata:
|
110
|
-
bug_tracker_uri:
|
110
|
+
bug_tracker_uri: https://github.com/palkan/n_plus_one_control/issues
|
111
111
|
changelog_uri: https://github.com/palkan/n_plus_one_control/blob/master/CHANGELOG.md
|
112
|
-
documentation_uri:
|
113
|
-
homepage_uri:
|
114
|
-
source_code_uri:
|
112
|
+
documentation_uri: https://github.com/palkan/n_plus_one_control
|
113
|
+
homepage_uri: https://github.com/palkan/n_plus_one_control
|
114
|
+
source_code_uri: https://github.com/palkan/n_plus_one_control
|
115
115
|
post_install_message:
|
116
116
|
rdoc_options: []
|
117
117
|
require_paths:
|
@@ -120,14 +120,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
120
|
requirements:
|
121
121
|
- - ">="
|
122
122
|
- !ruby/object:Gem::Version
|
123
|
-
version: 2.
|
123
|
+
version: 2.7.0
|
124
124
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
125
|
requirements:
|
126
126
|
- - ">="
|
127
127
|
- !ruby/object:Gem::Version
|
128
128
|
version: '0'
|
129
129
|
requirements: []
|
130
|
-
rubygems_version: 3.
|
130
|
+
rubygems_version: 3.4.6
|
131
131
|
signing_key:
|
132
132
|
specification_version: 4
|
133
133
|
summary: RSpec and Minitest matchers to prevent N+1 queries problem
|