n_plus_one_control 0.6.2 → 0.7.1
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.
- 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
|