n_plus_one_control 0.5.0 → 0.6.0

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: 394ec848692af46dd43c3a03a3315349c2aea19d174ff05ededa1ef825839ee0
4
- data.tar.gz: 7e2a271fe8ae173b117d1fc514e33b6f08aa7c528e4ea476f5360591a28da6f8
3
+ metadata.gz: 48ad0338e6227c2b71a09254f3613db415d64fe5b40ee7df55ee1daddc46c0f5
4
+ data.tar.gz: c55895975614627ea2308dde866079bad496f5ddef81f295267f5b0d8b2670ac
5
5
  SHA512:
6
- metadata.gz: 1f411b0c1539f517e0c4036570e274fe62327817722065551fce09307e15402c31185566343ddb9fd388324a71c5c8d7730fc938767397f5bbba60f103ffdccb
7
- data.tar.gz: 21a4445b6dd7efa9dad9bbfe2ccf8708ae813e2e60ea424b60f7b00cf58bef1971d58b1392893f34e817a3834792b461536890ed38619a20e0f9bd770ab7ddfd
6
+ metadata.gz: b4f6b44d7ecccd6e3f53d65e4df916982b144b8381049ebec5659cdf5792cc9c1d1c7fdee22cb8610df155020220286cc2daf78a0dbe8b76fe6bceb778446c81
7
+ data.tar.gz: 63ac722a3b82c6c3263a79f0065f554352bf4ce2043bdec98f37139f8b21f441e9327bc575ff6e3bdde05e8eac95ce636615e74ed4e3415a0ae28312da43d2a7
@@ -1,4 +1,7 @@
1
- ## master (unreleased)
1
+ ## 0.6.0 (2020-11-27)
2
+
3
+ - Fix table stats summary when queries use backticks to surround table names ([@andrewhampton][])
4
+ - Add support to test for linear query. ([@caalberts][])
2
5
 
3
6
  ## 0.5.0 (2020-09-07)
4
7
 
@@ -26,3 +29,5 @@ Could be specified via `NPLUSONE_BACKTRACE` env var.
26
29
 
27
30
  [@Earendil95]: https://github.com/Earendil95
28
31
  [@palkan]: https://github.com/palkan
32
+ [@caalberts]: https://github.com/caalberts
33
+ [@andrewhampton]: https://github.com/andrewhampton
data/README.md CHANGED
@@ -85,10 +85,10 @@ Availables modifiers:
85
85
  ```ruby
86
86
  # You can specify the RegExp to filter queries.
87
87
  # By default, it only considers SELECT queries.
88
- expect { subject }.to perform_constant_number_of_queries.matching(/INSERT/)
88
+ expect { get :index }.to perform_constant_number_of_queries.matching(/INSERT/)
89
89
 
90
90
  # You can also provide custom scale factors
91
- expect { subject }.to perform_constant_number_of_queries.with_scale_factors(10, 100)
91
+ expect { get :index }.to perform_constant_number_of_queries.with_scale_factors(10, 100)
92
92
  ```
93
93
 
94
94
  #### Using scale factor in spec
@@ -111,6 +111,21 @@ context "N+1", :n_plus_one do
111
111
  end
112
112
  ```
113
113
 
114
+ #### Other available matchers
115
+
116
+ `perform_linear_number_of_queries(slope: 1)` allows you to test that a query generates linear number of queries with the given slope.
117
+
118
+ ```ruby
119
+ context "when has linear query", :n_plus_one do
120
+ populate { |n| create_list(:post, n) }
121
+
122
+ specify do
123
+ expect { Post.find_each { |p| p.user.name } }
124
+ .to perform_linear_number_of_queries(slope: 1)
125
+ end
126
+ end
127
+ ```
128
+
114
129
  ### Minitest
115
130
 
116
131
  First, add NPlusOneControl to your `test_helper.rb`:
@@ -132,6 +147,18 @@ def test_no_n_plus_one_error
132
147
  end
133
148
  ```
134
149
 
150
+ You can also use `assert_perform_linear_number_of_queries` to test for linear queries:
151
+
152
+ ```ruby
153
+ def test_no_n_plus_one_error
154
+ populate = ->(n) { create_list(:post, n) }
155
+
156
+ assert_perform_linear_number_of_queries(slope: 1, populate: populate) do
157
+ Post.find_each { |p| p.user.name }
158
+ end
159
+ end
160
+ ```
161
+
135
162
  You can also specify custom scale factors or filter patterns:
136
163
 
137
164
  ```ruby
@@ -6,7 +6,7 @@ require "n_plus_one_control/executor"
6
6
  # RSpec and Minitest matchers to prevent N+1 queries problem.
7
7
  module NPlusOneControl
8
8
  # Used to extract a table name from a query
9
- EXTRACT_TABLE_RXP = /(insert into|update|delete from|from) ['"](\S+)['"]/i.freeze
9
+ EXTRACT_TABLE_RXP = /(insert into|update|delete from|from) ['"`](\S+)['"`]/i.freeze
10
10
 
11
11
  # Used to convert a query part extracted by the regexp above to the corresponding
12
12
  # human-friendly type
@@ -23,8 +23,13 @@ module NPlusOneControl
23
23
 
24
24
  attr_reader :default_matching
25
25
 
26
- def failure_message(queries) # rubocop:disable Metrics/MethodLength
27
- msg = ["Expected to make the same number of queries, but got:\n"]
26
+ FAILURE_MESSAGES = {
27
+ constant_queries: "Expected to make the same number of queries",
28
+ linear_queries: "Expected to make linear number of queries"
29
+ }
30
+
31
+ def failure_message(type, queries) # rubocop:disable Metrics/MethodLength
32
+ msg = ["#{FAILURE_MESSAGES[type]}, but got:\n"]
28
33
  queries.each do |(scale, data)|
29
34
  msg << " #{data.size} for N=#{scale}\n"
30
35
  end
@@ -26,7 +26,30 @@ module NPlusOneControl
26
26
 
27
27
  counts = queries.map(&:last).map(&:size)
28
28
 
29
- assert counts.max == counts.min, NPlusOneControl.failure_message(queries)
29
+ assert counts.max == counts.min, NPlusOneControl.failure_message(:constant_queries, queries)
30
+ end
31
+
32
+ def assert_perform_linear_number_of_queries(
33
+ slope: 1,
34
+ populate: nil,
35
+ matching: nil,
36
+ scale_factors: nil,
37
+ warmup: nil
38
+ )
39
+
40
+ raise ArgumentError, "Block is required" unless block_given?
41
+
42
+ warming_up warmup
43
+
44
+ @executor = NPlusOneControl::Executor.new(
45
+ population: populate || population_method,
46
+ matching: matching || NPlusOneControl.default_matching,
47
+ scale_factors: scale_factors || NPlusOneControl.default_scale_factors
48
+ )
49
+
50
+ queries = @executor.call { yield }
51
+
52
+ assert linear?(queries, slope: slope), NPlusOneControl.failure_message(:linear_queries, queries)
30
53
  end
31
54
 
32
55
  def current_scale
@@ -42,6 +65,16 @@ module NPlusOneControl
42
65
  def population_method
43
66
  methods.include?(:populate) ? method(:populate) : nil
44
67
  end
68
+
69
+ def linear?(queries, slope:)
70
+ queries.each_cons(2).all? do |pair|
71
+ scales = pair.map(&:first)
72
+ query_lists = pair.map(&:last)
73
+
74
+ actual_slope = (query_lists[1].size - query_lists[0].size) / (scales[1] - scales[0])
75
+ actual_slope <= slope
76
+ end
77
+ end
45
78
  end
46
79
  end
47
80
 
@@ -4,7 +4,8 @@ gem "rspec-core", ">= 3.5"
4
4
 
5
5
  require "n_plus_one_control"
6
6
  require "n_plus_one_control/rspec/dsl"
7
- require "n_plus_one_control/rspec/matcher"
7
+ require "n_plus_one_control/rspec/matchers/perform_constant_number_of_queries"
8
+ require "n_plus_one_control/rspec/matchers/perform_linear_number_of_queries"
8
9
  require "n_plus_one_control/rspec/context"
9
10
 
10
11
  module NPlusOneControl
@@ -46,6 +46,6 @@
46
46
  raise "This matcher doesn't support negation"
47
47
  end
48
48
 
49
- failure_message { |_actual| NPlusOneControl.failure_message(@queries) }
49
+ failure_message { |_actual| NPlusOneControl.failure_message(:constant_queries, @queries) }
50
50
  end
51
51
  # rubocop:enable Metrics/BlockLength
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/BlockLength
4
+ ::RSpec::Matchers.define :perform_linear_number_of_queries do |slope: 1|
5
+ supports_block_expectations
6
+
7
+ chain :with_scale_factors do |*factors|
8
+ @factors = factors
9
+ end
10
+
11
+ chain :matching do |pattern|
12
+ @pattern = pattern
13
+ end
14
+
15
+ chain :with_warming_up do
16
+ @warmup = true
17
+ end
18
+
19
+ match do |actual, *_args|
20
+ raise ArgumentError, "Block is required" unless actual.is_a? Proc
21
+
22
+ raise "Missing tag :n_plus_one" unless
23
+ @matcher_execution_context.respond_to?(:n_plus_one_populate)
24
+
25
+ populate = @matcher_execution_context.n_plus_one_populate
26
+ warmup = @warmup ? actual : @matcher_execution_context.n_plus_one_warmup
27
+
28
+ warmup.call if warmup.present?
29
+
30
+ @matcher_execution_context.executor = NPlusOneControl::Executor.new(
31
+ population: populate,
32
+ matching: nil,
33
+ scale_factors: @factors
34
+ )
35
+
36
+ @queries = @matcher_execution_context.executor.call(&actual)
37
+
38
+ @queries.each_cons(2).all? do |pair|
39
+ scales = pair.map(&:first)
40
+ query_lists = pair.map(&:last)
41
+
42
+ actual_slope = (query_lists[1].size - query_lists[0].size) / (scales[1] - scales[0])
43
+ actual_slope <= slope
44
+ end
45
+ end
46
+
47
+ match_when_negated do |_actual|
48
+ raise "This matcher doesn't support negation"
49
+ end
50
+
51
+ failure_message { |_actual| NPlusOneControl.failure_message(:linear_queries, @queries) }
52
+ 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.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
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.5.0
4
+ version: 0.6.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: 2020-09-07 00:00:00.000000000 Z
11
+ date: 2020-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -100,7 +100,8 @@ files:
100
100
  - lib/n_plus_one_control/rspec.rb
101
101
  - lib/n_plus_one_control/rspec/context.rb
102
102
  - lib/n_plus_one_control/rspec/dsl.rb
103
- - lib/n_plus_one_control/rspec/matcher.rb
103
+ - lib/n_plus_one_control/rspec/matchers/perform_constant_number_of_queries.rb
104
+ - lib/n_plus_one_control/rspec/matchers/perform_linear_number_of_queries.rb
104
105
  - lib/n_plus_one_control/version.rb
105
106
  homepage: http://github.com/palkan/n_plus_one_control
106
107
  licenses:
@@ -111,7 +112,7 @@ metadata:
111
112
  documentation_uri: http://github.com/palkan/n_plus_one_control
112
113
  homepage_uri: http://github.com/palkan/n_plus_one_control
113
114
  source_code_uri: http://github.com/palkan/n_plus_one_control
114
- post_install_message:
115
+ post_install_message:
115
116
  rdoc_options: []
116
117
  require_paths:
117
118
  - lib
@@ -126,8 +127,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
127
  - !ruby/object:Gem::Version
127
128
  version: '0'
128
129
  requirements: []
129
- rubygems_version: 3.0.6
130
- signing_key:
130
+ rubygems_version: 3.0.3
131
+ signing_key:
131
132
  specification_version: 4
132
133
  summary: RSpec and Minitest matchers to prevent N+1 queries problem
133
134
  test_files: []