n_plus_one_control 0.6.2 → 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: 95b1fada3d7db7a761650c8cd63b93b93a463dcdbfcf2df2b6e3ea25ac04355a
4
- data.tar.gz: 3a3937ad74d66ea0bc0e56b50954c585c36c401b67d9af4dfd2a94e4202ae0be
3
+ metadata.gz: 0fed1df4b3bee8844b57b4d4201802361b5871d93db3fdb0132a4073fd56fc3a
4
+ data.tar.gz: 56649719e9b94ed7eb05ccf3c6236399a6a2a9394ce37574a8e58f079b1ff3b4
5
5
  SHA512:
6
- metadata.gz: 9ce963308cf99f1c836e7d883a71dbab0b22f8326692444aa3c6639dfc87065fd3e6866ee1af5b358b42cbc67af4a1640677fc051264c8ba6bc35a1604953bd2
7
- data.tar.gz: f914d8e34d49f7ace51c872fde24487f216b481f39b8236b9c7d24bef26530ba5cbd1040b71d0b445a73f5769762e942fd3b00a7d4211a704fa9dcebf3bd34d9
6
+ metadata.gz: cae71e3487fb5210bd6dd76ae0cf7ee1bb310748a3e3a9cd95d893dc2eb99c6fa90c8482366897a7a2e6c8ed479780d337ef2e1284c266fef00e7b67f5f42dad
7
+ data.tar.gz: 270f6a75e499ffecd886d98739ccaa5f15ff60a7aebc1ebecc10cc0b2cfdd831137e482897d6ed217f0e60306e2b0350ef22d781a0475ad1a7dfda7563d6fc35
data/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
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
+
5
25
  ## 0.6.2 (2021-10-26)
6
26
 
7
27
  - Fix .ignore setting (.ignore setting was ignored by the Collector ;-))
@@ -44,3 +64,4 @@ Could be specified via `NPLUSONE_BACKTRACE` env var.
44
64
  [@palkan]: https://github.com/palkan
45
65
  [@caalberts]: https://github.com/caalberts
46
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
@@ -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
- 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
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 behavour for populate method
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 follwing option
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
- 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,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
- 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
 
@@ -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.2"
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.2
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-26 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,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: 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
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.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.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