n_plus_one_control 0.3.1 → 0.4.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: fa826fde3f47f08d3054ca3cf80a4c4d2a4ef2f70aa8c6f5d56a94dadb8ef656
4
- data.tar.gz: d8eb72c89592e99d3864b89fcdaa857fae1566ee4e2e5733fea50faa2463ba5c
3
+ metadata.gz: 227fb8257ab67628ffd449aff0358d729dd3ff3eafc291f9fca427aa355e0650
4
+ data.tar.gz: 2e65e5f09da3ad989fbb1e2418a657c8142ea8d25540a0daf729adeb9f0b0d5b
5
5
  SHA512:
6
- metadata.gz: 87a27c36da8bfb778027b3cbd2cad307b0ed1426b4b8273989fbe8171aace9efe7e90d8919d5d4e275d48d081c1bd0924cafbbfe87d750b28e04eb4cf3a57c4e
7
- data.tar.gz: dd8c8be25a92742551d955d258b26a8abcb2fba15f1040afa2b39f4fca096bdf17eff8b3f54e2975dae4dc63952c79197932d819a61edd7faed8d4a5f11cbf1d
6
+ metadata.gz: e79cf5bd9ec1afd8b10ca184a483edd9e8d8fbbeaca62e147d277102715ead2fa909d720eb0085fc6d2c2441abb5e61aa08f8a081a4b37079b3f552694d3e220
7
+ data.tar.gz: 59c76b177dc8b6ad4d58cfbad1f3c3e89204eef6e92d2d39989b5829f714939c77d0d38806e935d3624e20469447607f7b403af2b2bd633a02486e82c6cc4f6c
@@ -0,0 +1,8 @@
1
+ ## 0.4.0 (2020-07-20)
2
+
3
+ - Make scale factor available in tests via `#current_scale` method. ([@Earendil95][])
4
+
5
+ - Start keeping a changelog. ([@palkan][])
6
+
7
+ [@Earendil95]: https://github.com/Earendil95
8
+ [@palkan]: https://github.com/palkan
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017 palkan
3
+ Copyright (c) 2017-2020 Vladimir Dementyev
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -92,6 +92,26 @@ expect { ... }.to perform_constant_number_of_queries.matching(/INSERT/)
92
92
  expect { ... }.to perform_constant_number_of_queries.with_scale_factors(10, 100)
93
93
  ```
94
94
 
95
+ #### Using scale factor in spec
96
+
97
+ Let's suppose your action accepts parameter, which can make impact on the number of returned records:
98
+
99
+ ```ruby
100
+ get :index, params: { per_page: 10 }
101
+ ```
102
+
103
+ Then it is enough to just change `per_page` parameter between executions and do not recreate records in DB. For this purpose, you can use `current_scale` method in your example:
104
+
105
+ ```ruby
106
+ context "N+1", :n_plus_one do
107
+ before { create_list :post, 3 }
108
+
109
+ specify do
110
+ expect { get :index, params: { per_page: current_scale } }.to perform_constant_number_of_queries
111
+ end
112
+ end
113
+ ```
114
+
95
115
  ### Minitest
96
116
 
97
117
  First, add NPlusOneControl to your `test_helper.rb`:
@@ -147,6 +167,16 @@ def test_no_n_plus_one_error
147
167
  end
148
168
  ```
149
169
 
170
+ As in RSpec, you can use `current_scale` factor instead of `populate` block:
171
+
172
+ ```ruby
173
+ def test_no_n_plus_one_error
174
+ assert_perform_constant_number_of_queries do
175
+ get :index, params: { per_page: current_scale }
176
+ end
177
+ end
178
+ ```
179
+
150
180
  ### With caching
151
181
 
152
182
  If you use caching you can face the problem when first request performs more DB queries than others. The solution is:
@@ -3,7 +3,7 @@
3
3
  module NPlusOneControl
4
4
  # Runs code for every scale factor
5
5
  # and returns collected queries.
6
- module Executor
6
+ class Executor
7
7
  # Subscribes to ActiveSupport notifications and collect matching queries.
8
8
  class Collector
9
9
  def initialize(pattern)
@@ -29,37 +29,59 @@ module NPlusOneControl
29
29
  class << self
30
30
  attr_accessor :transaction_begin
31
31
  attr_accessor :transaction_rollback
32
+ end
33
+
34
+ attr_reader :current_scale
35
+
36
+ self.transaction_begin = -> do
37
+ ActiveRecord::Base.connection.begin_transaction(joinable: false)
38
+ end
39
+
40
+ self.transaction_rollback = -> do
41
+ ActiveRecord::Base.connection.rollback_transaction
42
+ end
32
43
 
33
- def call(population:, scale_factors: nil, matching: nil)
34
- raise ArgumentError, "Block is required!" unless block_given?
44
+ def initialize(population: nil, scale_factors: nil, matching: nil)
45
+ @population = population
46
+ @scale_factors = scale_factors
47
+ @matching = matching
48
+ end
49
+
50
+ # rubocop:disable Metrics/MethodLength
51
+ def call
52
+ raise ArgumentError, "Block is required!" unless block_given?
35
53
 
36
- results = []
37
- collector = Collector.new(matching)
54
+ results = []
55
+ collector = Collector.new(matching)
38
56
 
39
- (scale_factors || NPlusOneControl.default_scale_factors).each do |scale|
40
- with_transaction do
41
- population.call(scale)
42
- results << [scale, collector.call { yield }]
43
- end
57
+ (scale_factors || NPlusOneControl.default_scale_factors).each do |scale|
58
+ @current_scale = scale
59
+ with_transaction do
60
+ population&.call(scale)
61
+ results << [scale, collector.call { yield }]
44
62
  end
45
- results
46
63
  end
64
+ results
65
+ end
66
+ # rubocop:enable Metrics/MethodLength
47
67
 
48
- private
68
+ private
49
69
 
50
- def with_transaction
51
- transaction_begin.call
52
- yield
53
- ensure
54
- transaction_rollback.call
55
- end
70
+ def with_transaction
71
+ transaction_begin.call
72
+ yield
73
+ ensure
74
+ transaction_rollback.call
56
75
  end
57
76
 
58
- self.transaction_begin = -> do
59
- ActiveRecord::Base.connection.begin_transaction(joinable: false)
77
+ def transaction_begin
78
+ self.class.transaction_begin
60
79
  end
61
- self.transaction_rollback = -> do
62
- ActiveRecord::Base.connection.rollback_transaction
80
+
81
+ def transaction_rollback
82
+ self.class.transaction_rollback
63
83
  end
84
+
85
+ attr_reader :population, :scale_factors, :matching
64
86
  end
65
87
  end
@@ -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,16 +16,32 @@ module NPlusOneControl
20
16
 
21
17
  warming_up warmup
22
18
 
23
- queries = NPlusOneControl::Executor.call(
24
- population: populate || method(:populate),
19
+ @executor = NPlusOneControl::Executor.new(
20
+ population: population_proc(populate),
25
21
  matching: matching || /^SELECT/i,
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
29
  assert counts.max == counts.min, NPlusOneControl.failure_message(queries)
32
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_proc(populate)
43
+ (populate || methods.include?(:populate) ? method(:populate) : nil)
44
+ end
33
45
  end
34
46
  end
35
47
 
@@ -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
@@ -3,17 +3,8 @@
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
19
10
 
@@ -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
@@ -30,13 +30,14 @@
30
30
  # by default we're looking for select queries
31
31
  pattern = @pattern || /^SELECT/i
32
32
 
33
- @queries = NPlusOneControl::Executor.call(
33
+ @matcher_execution_context.executor = NPlusOneControl::Executor.new(
34
34
  population: populate,
35
35
  matching: pattern,
36
- scale_factors: @factors,
37
- &actual
36
+ scale_factors: @factors
38
37
  )
39
38
 
39
+ @queries = @matcher_execution_context.executor.call(&actual)
40
+
40
41
  counts = @queries.map(&:last).map(&:size)
41
42
 
42
43
  counts.max == counts.min
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NPlusOneControl
4
- VERSION = "0.3.1"
4
+ VERSION = "0.4.0"
5
5
  end
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: n_plus_one_control
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-18 00:00:00.000000000 Z
11
+ date: 2020-07-20 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
@@ -146,16 +146,9 @@ executables: []
146
146
  extensions: []
147
147
  extra_rdoc_files: []
148
148
  files:
149
- - ".gitignore"
150
- - ".rspec"
151
- - ".rubocop.yml"
152
- - ".travis.yml"
153
- - Gemfile
149
+ - CHANGELOG.md
154
150
  - LICENSE.txt
155
151
  - README.md
156
- - Rakefile
157
- - bin/console
158
- - bin/setup
159
152
  - lib/n_plus_one_control.rb
160
153
  - lib/n_plus_one_control/executor.rb
161
154
  - lib/n_plus_one_control/minitest.rb
@@ -164,19 +157,15 @@ files:
164
157
  - lib/n_plus_one_control/rspec/dsl.rb
165
158
  - lib/n_plus_one_control/rspec/matcher.rb
166
159
  - lib/n_plus_one_control/version.rb
167
- - n_plus_one_control.gemspec
168
- - spec/n_plus_one_control/executor_spec.rb
169
- - spec/n_plus_one_control/rspec_spec.rb
170
- - spec/n_plus_one_control_spec.rb
171
- - spec/spec_helper.rb
172
- - spec/support/post.rb
173
- - spec/support/user.rb
174
- - tests/minitest_test.rb
175
- - tests/test_helper.rb
176
160
  homepage: http://github.com/palkan/n_plus_one_control
177
161
  licenses:
178
162
  - MIT
179
- metadata: {}
163
+ metadata:
164
+ bug_tracker_uri: http://github.com/palkan/n_plus_one_control/issues
165
+ changelog_uri: https://github.com/palkan/n_plus_one_control/blob/master/CHANGELOG.md
166
+ documentation_uri: http://github.com/palkan/n_plus_one_control
167
+ homepage_uri: http://github.com/palkan/n_plus_one_control
168
+ source_code_uri: http://github.com/palkan/n_plus_one_control
180
169
  post_install_message:
181
170
  rdoc_options: []
182
171
  require_paths:
@@ -192,8 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
181
  - !ruby/object:Gem::Version
193
182
  version: '0'
194
183
  requirements: []
195
- rubyforge_project:
196
- rubygems_version: 2.7.6
184
+ rubygems_version: 3.0.3
197
185
  signing_key:
198
186
  specification_version: 4
199
187
  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,78 +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
- - 'vendor/**/*'
11
- - 'Rakefile'
12
- - 'Gemfile'
13
- - '*.gemspec'
14
- DisplayCopNames: true
15
- StyleGuideCopsOnly: false
16
- TargetRubyVersion: 2.4
17
-
18
- Rails:
19
- Enabled: false
20
-
21
- Naming/UncommunicativeMethodParamName:
22
- Enabled: false
23
-
24
- Naming/AccessorMethodName:
25
- Enabled: false
26
-
27
- Style/TrivialAccessors:
28
- Enabled: false
29
-
30
- Style/Documentation:
31
- Exclude:
32
- - 'spec/**/*.rb'
33
- - 'tests/**/*.rb'
34
-
35
- Style/StringLiterals:
36
- Enabled: false
37
-
38
- Style/RegexpLiteral:
39
- Enabled: false
40
-
41
- Style/Lambda:
42
- Enabled: false
43
-
44
- Layout/SpaceInsideStringInterpolation:
45
- EnforcedStyle: no_space
46
-
47
- Style/ClassAndModuleChildren:
48
- Enabled: false
49
-
50
- Style/BlockDelimiters:
51
- Exclude:
52
- - 'spec/**/*.rb'
53
-
54
- Lint/AmbiguousRegexpLiteral:
55
- Enabled: false
56
-
57
-
58
- Metrics/MethodLength:
59
- Exclude:
60
- - 'spec/**/*.rb'
61
-
62
- Metrics/AbcSize:
63
- Max: 20
64
-
65
- Metrics/LineLength:
66
- Max: 100
67
- Exclude:
68
- - 'spec/**/*.rb'
69
-
70
- Metrics/BlockLength:
71
- Exclude:
72
- - 'spec/**/*.rb'
73
-
74
- Rails/Date:
75
- Enabled: false
76
-
77
- Rails/TimeZone:
78
- Enabled: false
@@ -1,9 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.5.3
6
-
7
- before_install:
8
- - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
9
- - gem install bundler -v '< 2'
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in n_plus_one_control.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1,13 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
- require "rubocop/rake_task"
4
- require "rake/testtask"
5
-
6
- Rake::TestTask.new do |t|
7
- t.test_files = FileList['tests/**/*_test.rb']
8
- end
9
-
10
- RuboCop::RakeTask.new
11
- RSpec::Core::RakeTask.new(:spec)
12
-
13
- task :default => [:spec, :test, :rubocop]
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "n_plus_one_control"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
@@ -1,35 +0,0 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'n_plus_one_control/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "n_plus_one_control"
8
- spec.version = NPlusOneControl::VERSION
9
- spec.authors = ["palkan"]
10
- spec.email = ["dementiev.vm@gmail.com"]
11
-
12
- spec.summary = "RSpec and Minitest matchers to prevent N+1 queries problem"
13
- spec.required_ruby_version = '>= 2.0.0'
14
- spec.description = %{
15
- RSpec and Minitest matchers to prevent N+1 queries problem.
16
-
17
- Evaluates code under consideration several times with different scale factors
18
- to make sure that the number of DB queries behaves as expected (i.e. O(1) instead of O(N)).
19
- }
20
- spec.homepage = "http://github.com/palkan/n_plus_one_control"
21
- spec.license = "MIT"
22
-
23
- spec.files = `git ls-files`.split($/)
24
- spec.require_paths = ["lib"]
25
-
26
- spec.add_development_dependency "bundler", "~> 1.10"
27
- spec.add_development_dependency "rake", "~> 10.0"
28
- spec.add_development_dependency "rspec", "~> 3.5"
29
- spec.add_development_dependency "minitest", "~> 5.9"
30
- spec.add_development_dependency "factory_girl", "~> 4.8.0"
31
- spec.add_development_dependency "rubocop", "~> 0.61.0"
32
- spec.add_development_dependency "activerecord", "~> 5.1"
33
- spec.add_development_dependency "sqlite3", "~> 1.3.6"
34
- spec.add_development_dependency "pry-byebug"
35
- end
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
-
5
- describe NPlusOneControl::Executor do
6
- let(:populate) do
7
- ->(n) { create_list(:post, n) }
8
- end
9
-
10
- let(:observable) do
11
- -> { Post.find_each(&:user) }
12
- end
13
-
14
- it "raises when block is missing" do
15
- expect { described_class.call(population: populate) }
16
- .to raise_error(ArgumentError, "Block is required!")
17
- end
18
-
19
- it "raises when populate is missing" do
20
- expect { described_class.call(&observable) }
21
- .to raise_error(ArgumentError, /population/)
22
- end
23
-
24
- it "returns correct counts for default scales" do
25
- result = described_class.call(
26
- population: populate,
27
- &observable
28
- )
29
-
30
- expect(result.size).to eq 2
31
- expect(result.first[0]).to eq 2
32
- expect(result.first[1].size).to eq 3
33
- expect(result.last[0]).to eq 3
34
- expect(result.last[1].size).to eq 4
35
- end
36
-
37
- it "returns correct counts for custom scales" do
38
- result = described_class.call(
39
- population: populate,
40
- scale_factors: [5, 10, 100],
41
- &observable
42
- )
43
-
44
- expect(result.size).to eq 3
45
- expect(result.first[0]).to eq 5
46
- expect(result.first[1].size).to eq 6
47
- expect(result.second[0]).to eq 10
48
- expect(result.second[1].size).to eq 11
49
- expect(result.last[0]).to eq 100
50
- expect(result.last[1].size).to eq 101
51
- end
52
-
53
- it "returns correct counts with custom match" do
54
- result = described_class.call(
55
- population: populate,
56
- matching: /users/,
57
- &observable
58
- )
59
-
60
- expect(result.first[0]).to eq 2
61
- expect(result.first[1].size).to eq 2
62
- expect(result.last[0]).to eq 3
63
- expect(result.last[1].size).to eq 3
64
- end
65
- end
@@ -1,113 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
-
5
- describe NPlusOneControl::RSpec do
6
- context "when no N+1", :n_plus_one do
7
- populate { |n| create_list(:post, n) }
8
-
9
- specify do
10
- expect { Post.preload(:user).find_each { |p| p.user.name } }
11
- .to perform_constant_number_of_queries
12
- end
13
- end
14
-
15
- context "when has N+1", :n_plus_one do
16
- populate { |n| create_list(:post, n) }
17
-
18
- specify do
19
- expect do
20
- expect { Post.find_each { |p| p.user.name } }
21
- .to perform_constant_number_of_queries
22
- end.to raise_error(RSpec::Expectations::ExpectationNotMetError)
23
- end
24
- end
25
-
26
- context "when context is missing" do
27
- specify do
28
- expect do
29
- expect { subject }.to perform_constant_number_of_queries
30
- end.to raise_error(/missing tag/i)
31
- end
32
- end
33
-
34
- context "when populate is missing", :n_plus_one do
35
- specify do
36
- expect do
37
- expect { subject }.to perform_constant_number_of_queries
38
- end.to raise_error(/please provide populate/i)
39
- end
40
- end
41
-
42
- context "when negated" do
43
- specify do
44
- expect do
45
- expect { subject }.not_to perform_constant_number_of_queries
46
- end.to raise_error(/support negation/i)
47
- end
48
- end
49
-
50
- context "when verbose", :n_plus_one do
51
- populate { |n| create_list(:post, n) }
52
-
53
- around(:each) do |ex|
54
- NPlusOneControl.verbose = true
55
- ex.run
56
- NPlusOneControl.verbose = false
57
- end
58
-
59
- specify do
60
- expect do
61
- expect { Post.find_each { |p| p.user.name } }
62
- .to perform_constant_number_of_queries
63
- end.to raise_error(RSpec::Expectations::ExpectationNotMetError, /select .+ from/i)
64
- end
65
- end
66
-
67
- context "with scale_factors", :n_plus_one do
68
- populate { |n| create_list(:post, n) }
69
-
70
- specify do
71
- expect { Post.find_each { |p| p.user.name } }
72
- .to perform_constant_number_of_queries.with_scale_factors(1, 1)
73
- end
74
- end
75
-
76
- context "with matching", :n_plus_one do
77
- populate { |n| create_list(:post, n) }
78
-
79
- specify do
80
- expect { Post.find_each { |p| p.user.name } }
81
- .to perform_constant_number_of_queries.matching(/posts/)
82
- end
83
- end
84
-
85
- context 'with warming up', :n_plus_one do
86
- let(:cache) { double "cache" }
87
-
88
- before do
89
- allow(cache).to receive(:setup).and_return(:result)
90
- allow(NPlusOneControl::Executor).to receive(:call) { raise StandardError }
91
- end
92
-
93
- populate { |n| create_list(:post, n) }
94
-
95
- warmup { cache.setup }
96
-
97
- it "runs warmup before calling Executor" do
98
- expect(cache).to receive(:setup)
99
- expect do
100
- expect { Post.find_each(&:id) }.to perform_constant_number_of_queries
101
- end.to raise_error StandardError
102
- end
103
- end
104
-
105
- context 'with_warming_up', :n_plus_one do
106
- populate { |n| create_list(:post, n) }
107
-
108
- it "runs actual one more time" do
109
- expect(Post).to receive(:all).exactly(3).times
110
- expect { Post.all }.to perform_constant_number_of_queries.with_warming_up
111
- end
112
- end
113
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
-
5
- describe NPlusOneControl do
6
- it "has a version number" do
7
- expect(NPlusOneControl::VERSION).not_to be nil
8
- end
9
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
4
- require "n_plus_one_control/rspec"
5
- require "benchmark"
6
- require "active_record"
7
- require "factory_girl"
8
- require "pry-byebug"
9
-
10
- ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
11
-
12
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
13
-
14
- RSpec.configure do |config|
15
- config.mock_with :rspec
16
-
17
- config.order = :random
18
- config.filter_run focus: true
19
- config.run_all_when_everything_filtered = true
20
-
21
- config.include FactoryGirl::Syntax::Methods
22
-
23
- config.before(:each) do
24
- ActiveRecord::Base.connection.begin_transaction(joinable: false)
25
- end
26
-
27
- config.after(:each) do
28
- ActiveRecord::Base.connection.rollback_transaction
29
- end
30
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ActiveRecord::Schema.define do
4
- create_table :posts do |t|
5
- t.string :title
6
- t.integer :user_id
7
- end
8
- end
9
-
10
- class Post < ActiveRecord::Base
11
- belongs_to :user
12
- end
13
-
14
- FactoryGirl.define do
15
- factory :post do
16
- title "Title"
17
- user
18
- end
19
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ActiveRecord::Schema.define do
4
- create_table :users do |t|
5
- t.string :name
6
- end
7
- end
8
-
9
- class User < ActiveRecord::Base
10
- has_many :posts
11
- end
12
-
13
- FactoryGirl.define do
14
- factory :user do
15
- name "John"
16
- end
17
- end
@@ -1,63 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "test_helper"
4
-
5
- class TestMinitest < Minitest::Test
6
- def test_no_n_plus_one_error
7
- populate = ->(n) { create_list(:post, n) }
8
-
9
- assert_perform_constant_number_of_queries(populate: populate) do
10
- Post.preload(:user).find_each { |p| p.user.name }
11
- end
12
- end
13
-
14
- def test_with_n_plus_one_error
15
- populate = ->(n) { create_list(:post, n) }
16
-
17
- e = assert_raises Minitest::Assertion do
18
- assert_perform_constant_number_of_queries(populate: populate) do
19
- Post.find_each { |p| p.user.name }
20
- end
21
- end
22
-
23
- assert_match "Expected to make the same number of queries", e.message
24
- assert_match "3 for N=2", e.message
25
- assert_match "4 for N=3", e.message
26
- end
27
-
28
- def test_no_n_plus_one_error_with_scale_factors
29
- populate = ->(n) { create_list(:post, n) }
30
-
31
- assert_perform_constant_number_of_queries(
32
- populate: populate,
33
- scale_factors: [1, 1]
34
- ) do
35
- Post.find_each { |p| p.user.name }
36
- end
37
- end
38
-
39
- def test_no_n_plus_one_error_with_matching
40
- populate = ->(n) { create_list(:post, n) }
41
-
42
- assert_perform_constant_number_of_queries(
43
- populate: populate,
44
- matching: /posts/
45
- ) do
46
- Post.find_each { |p| p.user.name }
47
- end
48
- end
49
-
50
- def populate(n)
51
- create_list(:post, n)
52
- end
53
-
54
- def test_fallback_to_populate_method
55
- e = assert_raises Minitest::Assertion do
56
- assert_perform_constant_number_of_queries do
57
- Post.find_each { |p| p.user.name }
58
- end
59
- end
60
-
61
- assert_match "Expected to make the same number of queries", e.message
62
- end
63
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "minitest/autorun"
4
- require "minitest/pride"
5
-
6
- $LOAD_PATH << File.expand_path('../lib', __dir__)
7
- Thread.abort_on_exception = true
8
-
9
- require "n_plus_one_control/minitest"
10
- require "benchmark"
11
- require "active_record"
12
- require "factory_girl"
13
- require "pry-byebug"
14
-
15
- ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
16
-
17
- Dir["#{File.dirname(__FILE__)}/../spec/support/**/*.rb"].each { |f| require f }
18
-
19
- module TransactionalTests
20
- def setup
21
- ActiveRecord::Base.connection.begin_transaction(joinable: false)
22
- super
23
- end
24
-
25
- def teardown
26
- super
27
- ActiveRecord::Base.connection.rollback_transaction
28
- end
29
- end
30
-
31
- Minitest::Test.prepend TransactionalTests
32
- Minitest::Test.include FactoryGirl::Syntax::Methods