n_plus_one_control 0.3.1 → 0.4.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 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