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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +28 -0
- data/LICENSE.txt +1 -1
- data/README.md +127 -12
- data/lib/n_plus_one_control.rb +96 -5
- data/lib/n_plus_one_control/executor.rb +63 -23
- data/lib/n_plus_one_control/minitest.rb +24 -5
- data/lib/n_plus_one_control/railtie.rb +13 -0
- data/lib/n_plus_one_control/rspec.rb +4 -1
- data/lib/n_plus_one_control/rspec/context.rb +12 -11
- data/lib/n_plus_one_control/rspec/dsl.rb +24 -6
- data/lib/n_plus_one_control/rspec/matcher.rb +13 -5
- data/lib/n_plus_one_control/version.rb +1 -1
- metadata +19 -89
- data/.gitignore +0 -11
- data/.rspec +0 -2
- data/.rubocop.yml +0 -74
- data/.travis.yml +0 -5
- data/Gemfile +0 -4
- data/Rakefile +0 -13
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/n_plus_one_control.gemspec +0 -47
- data/spec/n_plus_one_control/executor_spec.rb +0 -65
- data/spec/n_plus_one_control/rspec_spec.rb +0 -84
- data/spec/n_plus_one_control_spec.rb +0 -9
- data/spec/spec_helper.rb +0 -30
- data/spec/support/post.rb +0 -19
- data/spec/support/user.rb +0 -17
- data/tests/minitest_test.rb +0 -63
- data/tests/test_helper.rb +0 -32
@@ -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
|
-
|
17
|
-
|
18
|
-
|
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
|
-
)
|
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
|
-
|
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
|
-
#
|
5
|
+
# Includes scale method into RSpec Example
|
6
6
|
module DSL
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
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
|
-
|
24
|
-
pattern = @pattern || /^SELECT/i
|
28
|
+
warmup.call if warmup.present?
|
25
29
|
|
26
|
-
|
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
|
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.
|
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:
|
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: '
|
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: '
|
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
|
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
|
-
-
|
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.
|
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
|
-
|
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
data/.rspec
DELETED
data/.rubocop.yml
DELETED
@@ -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
|