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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95b1fada3d7db7a761650c8cd63b93b93a463dcdbfcf2df2b6e3ea25ac04355a
4
- data.tar.gz: 3a3937ad74d66ea0bc0e56b50954c585c36c401b67d9af4dfd2a94e4202ae0be
3
+ metadata.gz: 3e2527bc8ec3ef67b4266b9a7221251d36f29f82697823eb6bba1e301031cd6f
4
+ data.tar.gz: b7117a64e62367c75ddd610c3c520ea7472f3d8f80d6e5f228c4642ed3ef2962
5
5
  SHA512:
6
- metadata.gz: 9ce963308cf99f1c836e7d883a71dbab0b22f8326692444aa3c6639dfc87065fd3e6866ee1af5b358b42cbc67af4a1640677fc051264c8ba6bc35a1604953bd2
7
- data.tar.gz: f914d8e34d49f7ace51c872fde24487f216b481f39b8236b9c7d24bef26530ba5cbd1040b71d0b445a73f5769762e942fd3b00a7d4211a704fa9dcebf3bd34d9
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-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,11 +64,13 @@ module NPlusOneControl
59
64
  private
60
65
 
61
66
  def warming_up(warmup)
62
- (warmup || methods.include?(:warmup) ? method(:warmup) : nil)&.call
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.include?(:populate) ? method(:populate) : nil
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, 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.1"
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.1
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