n_plus_one_control 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9eb89619e16cb318f7c758c355c9578b1bbe10b446aa86a8e6a05402e5a3634
4
- data.tar.gz: c441fc080ac62c150e21877868e8862175b22521d8cb12d3ce240d5153b2d7d7
3
+ metadata.gz: 0fed1df4b3bee8844b57b4d4201802361b5871d93db3fdb0132a4073fd56fc3a
4
+ data.tar.gz: 56649719e9b94ed7eb05ccf3c6236399a6a2a9394ce37574a8e58f079b1ff3b4
5
5
  SHA512:
6
- metadata.gz: 5e5e62dcd9958539060f05f25758a24cfedaa17d3b78a0439c0dc2fbc649c8ba9467371c56aecc78f722c0426daf70db8dea94e5ab8ea86bdc2cfec93c83669a
7
- data.tar.gz: 26cd504b1ca3ed20ce7b25f12a80ab3d8470a1ccefe66bc432af51005bbfd3bc30e3a770a04d7b4f528eac393581f56c2fa044034399c6741f7316b5444b22d2
6
+ metadata.gz: cae71e3487fb5210bd6dd76ae0cf7ee1bb310748a3e3a9cd95d893dc2eb99c6fa90c8482366897a7a2e6c8ed479780d337ef2e1284c266fef00e7b67f5f42dad
7
+ data.tar.gz: 270f6a75e499ffecd886d98739ccaa5f15ff60a7aebc1ebecc10cc0b2cfdd831137e482897d6ed217f0e60306e2b0350ef22d781a0475ad1a7dfda7563d6fc35
data/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.7.0 (2023-02-17)
6
+
7
+ - Added ability to specify the exact number of expected queries when using constant matchers. ([@akostadinov][], [@palkan][])
8
+
9
+ For RSpec, you can add the `.exactly` modifier:
10
+
11
+ ```ruby
12
+ expect { get :index }.to perform_constant_number_of_queries.exactly(1)
13
+ ```
14
+
15
+ For Minitest, you can provide the expected number of queries as the first argument:
16
+
17
+ ```ruby
18
+ assert_perform_constant_number_of_queries(0, **options) do
19
+ get :index
20
+ end
21
+ ```
22
+
23
+ - **Require Ruby 2.7+**.
24
+
25
+ ## 0.6.2 (2021-10-26)
26
+
27
+ - Fix .ignore setting (.ignore setting was ignored by the Collector ;-))
28
+ - Fix rspec matchers to allow expectations inside execution block
29
+
5
30
  ## 0.6.1 (2021-03-05)
6
31
 
7
32
  - Ruby 3.0 compatibility. ([@palkan][])
@@ -39,3 +64,4 @@ Could be specified via `NPLUSONE_BACKTRACE` env var.
39
64
  [@palkan]: https://github.com/palkan
40
65
  [@caalberts]: https://github.com/caalberts
41
66
  [@andrewhampton]: https://github.com/andrewhampton
67
+ [@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-2020 Vladimir Dementyev
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
@@ -111,6 +137,20 @@ context "N+1", :n_plus_one do
111
137
  end
112
138
  ```
113
139
 
140
+ ### Expectations in execution block
141
+
142
+ Both rspec matchers allows you to put additional expectations inside execution block to ensure that tested piece of code actually does what expected.
143
+
144
+ ```ruby
145
+ context "N+1", :n_plus_one do
146
+ specify do
147
+ expect do
148
+ expect(my_query).to eq(actuall_results)
149
+ end.to perform_constant_number_of_queries
150
+ end
151
+ end
152
+ ```
153
+
114
154
  #### Other available matchers
115
155
 
116
156
  `perform_linear_number_of_queries(slope: 1)` allows you to test that a query generates linear number of queries with the given slope.
@@ -152,7 +192,7 @@ You can also use `assert_perform_linear_number_of_queries` to test for linear qu
152
192
  ```ruby
153
193
  def test_no_n_plus_one_error
154
194
  populate = ->(n) { create_list(:post, n) }
155
-
195
+
156
196
  assert_perform_linear_number_of_queries(slope: 1, populate: populate) do
157
197
  Post.find_each { |p| p.user.name }
158
198
  end
@@ -177,6 +217,14 @@ assert_perform_constant_number_of_queries(
177
217
  end
178
218
  ```
179
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
+
180
228
  It's possible to specify a filter via `NPLUSONE_FILTER` env var, e.g.:
181
229
 
182
230
  ```ruby
@@ -256,7 +304,7 @@ end
256
304
  If your `warmup` and testing procs are identical, you can use:
257
305
 
258
306
  ```ruby
259
- expext { get :index }.to perform_constant_number_of_queries.with_warming_up # RSpec only
307
+ expect { get :index }.to perform_constant_number_of_queries.with_warming_up # RSpec only
260
308
  ```
261
309
 
262
310
  ### Configuration
@@ -288,7 +336,7 @@ NPlusOneControl.ignore = /^(BEGIN|COMMIT|SAVEPOINT|RELEASE)/
288
336
  # but can also track rom-rb events ('sql.rom') as well.
289
337
  NPlusOneControl.event = "sql.active_record"
290
338
 
291
- # configure transactional behavour for populate method
339
+ # configure transactional behaviour for populate method
292
340
  # in case of use multiple database connections
293
341
  NPlusOneControl::Executor.tap do |executor|
294
342
  connections = ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
@@ -312,7 +360,7 @@ NPlusOneControl.backtrace_cleaner = ->(locations_array) { do_some_filtering(loca
312
360
  NPlusOneControl.backtrace_length = 1
313
361
 
314
362
  # Sometime queries could be too large to provide any meaningful insight.
315
- # You can configure an output length limit for quries in verbose mode by setting the follwing option
363
+ # You can configure an output length limit for quries in verbose mode by setting the following option
316
364
  # NOTE: It could be specified via NPLUSONE_TRUNCATE env var
317
365
  NPlusOneControl.truncate_query_size = 100
318
366
  ```
@@ -21,6 +21,7 @@ module NPlusOneControl
21
21
 
22
22
  def callback(_name, _start, _finish, _message_id, values) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/LineLength
23
23
  return if %w[CACHE SCHEMA].include? values[:name]
24
+ return if values[:sql].match?(NPlusOneControl.ignore)
24
25
 
25
26
  return unless @pattern.nil? || (values[:sql] =~ @pattern)
26
27
 
@@ -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
- assert counts.max == counts.min, NPlusOneControl.failure_message(:constant_queries, queries)
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,7 +64,7 @@ module NPlusOneControl
59
64
  private
60
65
 
61
66
  def warming_up(warmup)
62
- (warmup || methods.include?(:warmup) ? method(:warmup) : nil)&.call
67
+ (warmup || (methods.include?(:warmup) ? method(:warmup) : nil))&.call
63
68
  end
64
69
 
65
70
  def population_method
@@ -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, wich will run before matching
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,11 +11,15 @@
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
18
21
 
19
- match do |actual, *_args|
22
+ match(notify_expectation_failures: true) do |actual, *_args|
20
23
  raise ArgumentError, "Block is required" unless actual.is_a? Proc
21
24
 
22
25
  raise "Missing tag :n_plus_one" unless
@@ -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
- counts.max == counts.min
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
 
@@ -16,7 +15,7 @@
16
15
  @warmup = true
17
16
  end
18
17
 
19
- match do |actual, *_args|
18
+ match(notify_expectation_failures: true) do |actual, *_args|
20
19
  raise ArgumentError, "Block is required" unless actual.is_a? Proc
21
20
 
22
21
  raise "Missing tag :n_plus_one" unless
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NPlusOneControl
4
- VERSION = "0.6.1"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -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[k]
63
+ next if before[k] == after&.fetch(k, nil)
63
64
 
64
- msg << "#{k}: #{before[k]} != #{after[k]}\n"
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.6.1
4
+ version: 0.7.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: 2021-03-05 00:00:00.000000000 Z
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: factory_girl
70
+ name: factory_bot
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 4.8.0
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: 4.8.0
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,16 +103,16 @@ 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: http://github.com/palkan/n_plus_one_control
106
+ homepage: https://github.com/palkan/n_plus_one_control
107
107
  licenses:
108
108
  - MIT
109
109
  metadata:
110
- bug_tracker_uri: http://github.com/palkan/n_plus_one_control/issues
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: http://github.com/palkan/n_plus_one_control
113
- homepage_uri: http://github.com/palkan/n_plus_one_control
114
- source_code_uri: http://github.com/palkan/n_plus_one_control
115
- post_install_message:
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
+ post_install_message:
116
116
  rdoc_options: []
117
117
  require_paths:
118
118
  - lib
@@ -120,15 +120,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
120
120
  requirements:
121
121
  - - ">="
122
122
  - !ruby/object:Gem::Version
123
- version: 2.5.0
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.0.6
131
- signing_key:
130
+ rubygems_version: 3.4.6
131
+ signing_key:
132
132
  specification_version: 4
133
133
  summary: RSpec and Minitest matchers to prevent N+1 queries problem
134
134
  test_files: []