n_plus_one_control 0.2.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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