n_plus_one_control 0.3.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.
@@ -5,10 +5,6 @@ require "n_plus_one_control"
5
5
  module NPlusOneControl
6
6
  # Minitest assertions
7
7
  module MinitestHelper
8
- def warming_up(warmup)
9
- (warmup || methods.include?(:warmup) ? method(:warmup) : nil)&.call
10
- end
11
-
12
8
  def assert_perform_constant_number_of_queries(
13
9
  populate: nil,
14
10
  matching: nil,
@@ -20,15 +16,64 @@ module NPlusOneControl
20
16
 
21
17
  warming_up warmup
22
18
 
23
- queries = NPlusOneControl::Executor.call(
24
- population: populate || method(:populate),
25
- matching: matching || /^SELECT/i,
19
+ @executor = NPlusOneControl::Executor.new(
20
+ population: populate || population_method,
21
+ matching: matching || NPlusOneControl.default_matching,
26
22
  scale_factors: scale_factors || NPlusOneControl.default_scale_factors
27
- ) { yield }
23
+ )
24
+
25
+ queries = @executor.call { yield }
28
26
 
29
27
  counts = queries.map(&:last).map(&:size)
30
28
 
31
- 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)
53
+ end
54
+
55
+ def current_scale
56
+ @executor&.current_scale
57
+ end
58
+
59
+ private
60
+
61
+ def warming_up(warmup)
62
+ (warmup || methods.include?(:warmup) ? method(:warmup) : nil)&.call
63
+ end
64
+
65
+ def population_method
66
+ methods.include?(:populate) ? method(:populate) : nil
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
32
77
  end
33
78
  end
34
79
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NPlusOneControl # :nodoc:
4
+ class Railtie < ::Rails::Railtie # :nodoc:
5
+ initializer "n_plus_one_control.backtrace_cleaner" do
6
+ ActiveSupport.on_load(:active_record) do
7
+ NPlusOneControl.backtrace_cleaner = lambda do |locations|
8
+ ::Rails.backtrace_cleaner.clean(locations)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ gem "rspec-core", ">= 3.5"
4
+
3
5
  require "n_plus_one_control"
4
6
  require "n_plus_one_control/rspec/dsl"
5
- 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"
6
9
  require "n_plus_one_control/rspec/context"
7
10
 
8
11
  module NPlusOneControl
@@ -11,5 +14,6 @@ module NPlusOneControl
11
14
  end
12
15
 
13
16
  ::RSpec.configure do |config|
14
- config.extend NPlusOneControl::RSpec::DSL, n_plus_one: true
17
+ config.extend NPlusOneControl::RSpec::DSL::ClassMethods, n_plus_one: true
18
+ config.include NPlusOneControl::RSpec::DSL, n_plus_one: true
15
19
  end
@@ -1,24 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- ::RSpec.shared_context "n_plus_one_control", n_plus_one: true do
3
+ RSpec.shared_context "n_plus_one_control" do
4
4
  # Helper to access populate block from within example/matcher
5
5
  let(:n_plus_one_populate) do |ex|
6
- if ex.example_group.populate.nil?
7
- raise(
8
- <<-MSG
9
- Populate block is missing!
6
+ return if ex.example_group.populate.nil?
10
7
 
11
- Please provide populate callback, e.g.:
12
-
13
- populate { |n| n.times { create_some_stuff } }
14
- MSG
15
- )
16
- end
17
8
  ->(n) { ex.instance_exec(n, &ex.example_group.populate) }
18
9
  end
19
10
 
20
11
  let(:n_plus_one_warmup) do |ex|
21
12
  return if ex.example_group.warmup.nil?
13
+
22
14
  -> { ex.instance_exec(&ex.example_group.warmup) }
23
15
  end
24
16
  end
17
+
18
+ RSpec.configure do |config|
19
+ config.include_context "n_plus_one_control", n_plus_one: true
20
+ end
@@ -2,23 +2,32 @@
2
2
 
3
3
  module NPlusOneControl
4
4
  module RSpec
5
- # Extends RSpec ExampleGroup with populate & warmup methods
5
+ # Includes scale method into RSpec Example
6
6
  module DSL
7
- # Setup warmup block, wich will run before matching
8
- # for example, if using cache, then later queries
9
- # will perform less DB queries than first
10
- def warmup
11
- return @warmup unless block_given?
7
+ # Extends RSpec ExampleGroup with populate & warmup methods
8
+ module ClassMethods
9
+ # Setup warmup block, wich will run before matching
10
+ # for example, if using cache, then later queries
11
+ # will perform less DB queries than first
12
+ def warmup
13
+ return @warmup unless block_given?
12
14
 
13
- @warmup = Proc.new
15
+ @warmup = Proc.new
16
+ end
17
+
18
+ # Setup populate callback, which is used
19
+ # to prepare data for each run.
20
+ def populate
21
+ return @populate unless block_given?
22
+
23
+ @populate = Proc.new
24
+ end
14
25
  end
15
26
 
16
- # Setup populate callback, which is used
17
- # to prepare data for each run.
18
- def populate
19
- return @populate unless block_given?
27
+ attr_accessor :executor
20
28
 
21
- @populate = Proc.new
29
+ def current_scale
30
+ executor&.current_scale
22
31
  end
23
32
  end
24
33
  end
@@ -27,16 +27,16 @@
27
27
 
28
28
  warmup.call if warmup.present?
29
29
 
30
- # by default we're looking for select queries
31
- pattern = @pattern || /^SELECT/i
30
+ pattern = @pattern || NPlusOneControl.default_matching
32
31
 
33
- @queries = NPlusOneControl::Executor.call(
32
+ @matcher_execution_context.executor = NPlusOneControl::Executor.new(
34
33
  population: populate,
35
34
  matching: pattern,
36
- scale_factors: @factors,
37
- &actual
35
+ scale_factors: @factors
38
36
  )
39
37
 
38
+ @queries = @matcher_execution_context.executor.call(&actual)
39
+
40
40
  counts = @queries.map(&:last).map(&:size)
41
41
 
42
42
  counts.max == counts.min
@@ -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.3.0"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: n_plus_one_control
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.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: 2018-06-19 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
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.10'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.10'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -80,107 +80,39 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: 4.8.0
83
- - !ruby/object:Gem::Dependency
84
- name: rubocop
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '0.49'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '0.49'
97
- - !ruby/object:Gem::Dependency
98
- name: activerecord
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '5.1'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '5.1'
111
- - !ruby/object:Gem::Dependency
112
- name: sqlite3
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
- - !ruby/object:Gem::Dependency
126
- name: pry-byebug
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '0'
139
83
  description: "\n RSpec and Minitest matchers to prevent N+1 queries problem.\n\n
140
84
  \ Evaluates code under consideration several times with different scale factors\n
141
85
  \ to make sure that the number of DB queries behaves as expected (i.e. O(1) instead
142
- of O(N)).\n\n Example:\n\n ```ruby\n context \"N+1\", :n_plus_one do\n
143
- \ populate { |n| create_list(:post, n) }\n\n specify do\n expect
144
- { get :index }.to perform_constant_number_of_queries\n end\n end\n ```\n
145
- \ "
86
+ of O(N)).\n "
146
87
  email:
147
88
  - dementiev.vm@gmail.com
148
89
  executables: []
149
90
  extensions: []
150
91
  extra_rdoc_files: []
151
92
  files:
152
- - ".gitignore"
153
- - ".rspec"
154
- - ".rubocop.yml"
155
- - ".travis.yml"
156
- - Gemfile
93
+ - CHANGELOG.md
157
94
  - LICENSE.txt
158
95
  - README.md
159
- - Rakefile
160
- - bin/console
161
- - bin/setup
162
96
  - lib/n_plus_one_control.rb
163
97
  - lib/n_plus_one_control/executor.rb
164
98
  - lib/n_plus_one_control/minitest.rb
99
+ - lib/n_plus_one_control/railtie.rb
165
100
  - lib/n_plus_one_control/rspec.rb
166
101
  - lib/n_plus_one_control/rspec/context.rb
167
102
  - lib/n_plus_one_control/rspec/dsl.rb
168
- - 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
169
105
  - lib/n_plus_one_control/version.rb
170
- - n_plus_one_control.gemspec
171
- - spec/n_plus_one_control/executor_spec.rb
172
- - spec/n_plus_one_control/rspec_spec.rb
173
- - spec/n_plus_one_control_spec.rb
174
- - spec/spec_helper.rb
175
- - spec/support/post.rb
176
- - spec/support/user.rb
177
- - tests/minitest_test.rb
178
- - tests/test_helper.rb
179
106
  homepage: http://github.com/palkan/n_plus_one_control
180
107
  licenses:
181
108
  - MIT
182
- metadata: {}
183
- post_install_message:
109
+ metadata:
110
+ bug_tracker_uri: http://github.com/palkan/n_plus_one_control/issues
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:
184
116
  rdoc_options: []
185
117
  require_paths:
186
118
  - lib
@@ -188,16 +120,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
188
120
  requirements:
189
121
  - - ">="
190
122
  - !ruby/object:Gem::Version
191
- version: 2.0.0
123
+ version: 2.5.0
192
124
  required_rubygems_version: !ruby/object:Gem::Requirement
193
125
  requirements:
194
126
  - - ">="
195
127
  - !ruby/object:Gem::Version
196
128
  version: '0'
197
129
  requirements: []
198
- rubyforge_project:
199
- rubygems_version: 2.7.6
200
- signing_key:
130
+ rubygems_version: 3.0.3
131
+ signing_key:
201
132
  specification_version: 4
202
133
  summary: RSpec and Minitest matchers to prevent N+1 queries problem
203
134
  test_files: []