n_plus_one_control 0.2.1 → 0.5.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.
@@ -8,21 +8,40 @@ module NPlusOneControl
8
8
  def assert_perform_constant_number_of_queries(
9
9
  populate: nil,
10
10
  matching: nil,
11
- scale_factors: nil
11
+ scale_factors: nil,
12
+ warmup: nil
12
13
  )
13
14
 
14
15
  raise ArgumentError, "Block is required" unless block_given?
15
16
 
16
- queries = NPlusOneControl::Executor.call(
17
- population: populate || method(:populate),
18
- matching: matching || /^SELECT/i,
17
+ warming_up warmup
18
+
19
+ @executor = NPlusOneControl::Executor.new(
20
+ population: populate || population_method,
21
+ matching: matching || NPlusOneControl.default_matching,
19
22
  scale_factors: scale_factors || NPlusOneControl.default_scale_factors
20
- ) { yield }
23
+ )
24
+
25
+ queries = @executor.call { yield }
21
26
 
22
27
  counts = queries.map(&:last).map(&:size)
23
28
 
24
29
  assert counts.max == counts.min, NPlusOneControl.failure_message(queries)
25
30
  end
31
+
32
+ def current_scale
33
+ @executor&.current_scale
34
+ end
35
+
36
+ private
37
+
38
+ def warming_up(warmup)
39
+ (warmup || methods.include?(:warmup) ? method(:warmup) : nil)&.call
40
+ end
41
+
42
+ def population_method
43
+ methods.include?(:populate) ? method(:populate) : nil
44
+ end
26
45
  end
27
46
  end
28
47
 
@@ -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,5 +1,7 @@
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
7
  require "n_plus_one_control/rspec/matcher"
@@ -11,5 +13,6 @@ module NPlusOneControl
11
13
  end
12
14
 
13
15
  ::RSpec.configure do |config|
14
- config.extend NPlusOneControl::RSpec::DSL, n_plus_one: true
16
+ config.extend NPlusOneControl::RSpec::DSL::ClassMethods, n_plus_one: true
17
+ config.include NPlusOneControl::RSpec::DSL, n_plus_one: true
15
18
  end
@@ -1,19 +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
10
+
11
+ let(:n_plus_one_warmup) do |ex|
12
+ return if ex.example_group.warmup.nil?
13
+
14
+ -> { ex.instance_exec(&ex.example_group.warmup) }
15
+ end
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.include_context "n_plus_one_control", n_plus_one: true
19
20
  end
@@ -2,14 +2,32 @@
2
2
 
3
3
  module NPlusOneControl
4
4
  module RSpec
5
- # Extends RSpec ExampleGroup with populate method
5
+ # Includes scale method into RSpec Example
6
6
  module DSL
7
- # Setup populate callback, which is used
8
- # to prepare data for each run.
9
- def populate
10
- return @populate 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?
11
14
 
12
- @populate = 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
25
+ end
26
+
27
+ attr_accessor :executor
28
+
29
+ def current_scale
30
+ executor&.current_scale
13
31
  end
14
32
  end
15
33
  end
@@ -12,6 +12,10 @@
12
12
  @pattern = pattern
13
13
  end
14
14
 
15
+ chain :with_warming_up do
16
+ @warmup = true
17
+ end
18
+
15
19
  match do |actual, *_args|
16
20
  raise ArgumentError, "Block is required" unless actual.is_a? Proc
17
21
 
@@ -19,17 +23,20 @@
19
23
  @matcher_execution_context.respond_to?(:n_plus_one_populate)
20
24
 
21
25
  populate = @matcher_execution_context.n_plus_one_populate
26
+ warmup = @warmup ? actual : @matcher_execution_context.n_plus_one_warmup
22
27
 
23
- # by default we're looking for select queries
24
- pattern = @pattern || /^SELECT/i
28
+ warmup.call if warmup.present?
25
29
 
26
- @queries = NPlusOneControl::Executor.call(
30
+ pattern = @pattern || NPlusOneControl.default_matching
31
+
32
+ @matcher_execution_context.executor = NPlusOneControl::Executor.new(
27
33
  population: populate,
28
34
  matching: pattern,
29
- scale_factors: @factors,
30
- &actual
35
+ scale_factors: @factors
31
36
  )
32
37
 
38
+ @queries = @matcher_execution_context.executor.call(&actual)
39
+
33
40
  counts = @queries.map(&:last).map(&:size)
34
41
 
35
42
  counts.max == counts.min
@@ -41,3 +48,4 @@
41
48
 
42
49
  failure_message { |_actual| NPlusOneControl.failure_message(@queries) }
43
50
  end
51
+ # rubocop:enable Metrics/BlockLength
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NPlusOneControl
4
- VERSION = "0.2.1"
4
+ VERSION = "0.5.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.2.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-31 00:00:00.000000000 Z
11
+ date: 2020-09-07 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,106 +80,37 @@ 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
103
  - lib/n_plus_one_control/rspec/matcher.rb
169
104
  - 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
105
  homepage: http://github.com/palkan/n_plus_one_control
180
106
  licenses:
181
107
  - MIT
182
- metadata: {}
108
+ metadata:
109
+ bug_tracker_uri: http://github.com/palkan/n_plus_one_control/issues
110
+ changelog_uri: https://github.com/palkan/n_plus_one_control/blob/master/CHANGELOG.md
111
+ documentation_uri: http://github.com/palkan/n_plus_one_control
112
+ homepage_uri: http://github.com/palkan/n_plus_one_control
113
+ source_code_uri: http://github.com/palkan/n_plus_one_control
183
114
  post_install_message:
184
115
  rdoc_options: []
185
116
  require_paths:
@@ -188,15 +119,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
188
119
  requirements:
189
120
  - - ">="
190
121
  - !ruby/object:Gem::Version
191
- version: 2.0.0
122
+ version: 2.5.0
192
123
  required_rubygems_version: !ruby/object:Gem::Requirement
193
124
  requirements:
194
125
  - - ">="
195
126
  - !ruby/object:Gem::Version
196
127
  version: '0'
197
128
  requirements: []
198
- rubyforge_project:
199
- rubygems_version: 2.6.13
129
+ rubygems_version: 3.0.6
200
130
  signing_key:
201
131
  specification_version: 4
202
132
  summary: RSpec and Minitest matchers to prevent N+1 queries problem
data/.gitignore DELETED
@@ -1,11 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- /log/
11
- *.gem
data/.rspec DELETED
@@ -1,2 +0,0 @@
1
- --format documentation
2
- --color
@@ -1,74 +0,0 @@
1
- AllCops:
2
- Include:
3
- - 'lib/**/*.rb'
4
- - 'lib/**/*.rake'
5
- - 'spec/**/*.rb'
6
- Exclude:
7
- - 'bin/**/*'
8
- - 'spec/dummy/**/*'
9
- - 'tmp/**/*'
10
- - 'Rakefile'
11
- - 'Gemfile'
12
- - '*.gemspec'
13
- DisplayCopNames: true
14
- StyleGuideCopsOnly: false
15
- TargetRubyVersion: 2.4
16
-
17
- Rails:
18
- Enabled: false
19
-
20
- Style/AccessorMethodName:
21
- Enabled: false
22
-
23
- Style/TrivialAccessors:
24
- Enabled: false
25
-
26
- Style/Documentation:
27
- Exclude:
28
- - 'spec/**/*.rb'
29
- - 'tests/**/*.rb'
30
-
31
- Style/StringLiterals:
32
- Enabled: false
33
-
34
- Style/RegexpLiteral:
35
- Enabled: false
36
-
37
- Style/Lambda:
38
- Enabled: false
39
-
40
- Style/SpaceInsideStringInterpolation:
41
- EnforcedStyle: no_space
42
-
43
- Style/ClassAndModuleChildren:
44
- Enabled: false
45
-
46
- Style/BlockDelimiters:
47
- Exclude:
48
- - 'spec/**/*.rb'
49
-
50
- Lint/AmbiguousRegexpLiteral:
51
- Enabled: false
52
-
53
-
54
- Metrics/MethodLength:
55
- Exclude:
56
- - 'spec/**/*.rb'
57
-
58
- Metrics/AbcSize:
59
- Max: 20
60
-
61
- Metrics/LineLength:
62
- Max: 100
63
- Exclude:
64
- - 'spec/**/*.rb'
65
-
66
- Metrics/BlockLength:
67
- Exclude:
68
- - 'spec/**/*.rb'
69
-
70
- Rails/Date:
71
- Enabled: false
72
-
73
- Rails/TimeZone:
74
- Enabled: false