n_plus_one_control 0.5.0 → 0.6.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: 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: []