mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #41704 from dinahshi/strict-loading-mode
Add n_plus_one_only mode to Core#strict_loading!
This commit is contained in:
commit
44c3ce2207
4 changed files with 93 additions and 10 deletions
|
@ -1,3 +1,22 @@
|
||||||
|
* Add mode argument to record level `strict_loading!`
|
||||||
|
|
||||||
|
This argument can be used when enabling strict loading for a single record
|
||||||
|
to specify that we only want to raise on n plus one queries.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
developer.strict_loading!(mode: :n_plus_one_only)
|
||||||
|
|
||||||
|
developer.projects.to_a # Does not raise
|
||||||
|
developer.projects.first.client # Raises StrictLoadingViolationError
|
||||||
|
```
|
||||||
|
|
||||||
|
Previously, enabling strict loading would cause any lazily loaded
|
||||||
|
association to raise an error. Using `n_plus_one_only` mode allows us to
|
||||||
|
lazily load belongs_to, has_many, and other associations that are fetched
|
||||||
|
through a single query.
|
||||||
|
|
||||||
|
*Dinah Shi*
|
||||||
|
|
||||||
* Prevent double saves in autosave of cyclic associations.
|
* Prevent double saves in autosave of cyclic associations.
|
||||||
|
|
||||||
Adds an internal saving state which tracks if a record is currently being saved.
|
Adds an internal saving state which tracks if a record is currently being saved.
|
||||||
|
|
|
@ -213,7 +213,7 @@ module ActiveRecord
|
||||||
|
|
||||||
private
|
private
|
||||||
def find_target
|
def find_target
|
||||||
if strict_loading? && owner.validation_context.nil?
|
if violates_strict_loading? && owner.validation_context.nil?
|
||||||
Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
|
Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -226,13 +226,20 @@ module ActiveRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
binds = AssociationScope.get_bind_values(owner, reflection.chain)
|
||||||
sc.execute(binds, klass.connection) { |record| set_inverse_instance(record) }
|
sc.execute(binds, klass.connection) do |record|
|
||||||
|
set_inverse_instance(record)
|
||||||
|
if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
|
||||||
|
record.strict_loading!
|
||||||
|
else
|
||||||
|
record.strict_loading_mode = owner.strict_loading_mode
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def strict_loading?
|
def violates_strict_loading?
|
||||||
return reflection.strict_loading? if reflection.options.key?(:strict_loading)
|
return reflection.strict_loading? if reflection.options.key?(:strict_loading)
|
||||||
|
|
||||||
owner.strict_loading?
|
owner.strict_loading? && !owner.strict_loading_n_plus_one_only?
|
||||||
end
|
end
|
||||||
|
|
||||||
# The scope for this association.
|
# The scope for this association.
|
||||||
|
|
|
@ -140,6 +140,7 @@ module ActiveRecord
|
||||||
mattr_accessor :action_on_strict_loading_violation, instance_accessor: false, default: :raise
|
mattr_accessor :action_on_strict_loading_violation, instance_accessor: false, default: :raise
|
||||||
|
|
||||||
class_attribute :strict_loading_by_default, instance_accessor: false, default: false
|
class_attribute :strict_loading_by_default, instance_accessor: false, default: false
|
||||||
|
class_attribute :strict_loading_mode, instance_accessor: true, default: []
|
||||||
|
|
||||||
mattr_accessor :writing_role, instance_accessor: false, default: :writing
|
mattr_accessor :writing_role, instance_accessor: false, default: :writing
|
||||||
|
|
||||||
|
@ -735,15 +736,30 @@ module ActiveRecord
|
||||||
# user.comments
|
# user.comments
|
||||||
# => ActiveRecord::StrictLoadingViolationError
|
# => ActiveRecord::StrictLoadingViolationError
|
||||||
#
|
#
|
||||||
# strict_loading! accepts a boolean argument to specify whether
|
# === Parameters:
|
||||||
# to enable or disable strict loading mode.
|
#
|
||||||
|
# * value - Boolean specifying whether to enable or disable strict loading.
|
||||||
|
# * mode - Symbol specifying strict loading mode. Defaults to :all. Using
|
||||||
|
# :n_plus_one_only mode will only raise an error if an association
|
||||||
|
# that will lead to an n plus one query is lazily loaded.
|
||||||
|
#
|
||||||
|
# === Example:
|
||||||
#
|
#
|
||||||
# user = User.first
|
# user = User.first
|
||||||
# user.strict_loading!(false) # => false
|
# user.strict_loading!(false) # => false
|
||||||
# user.comments
|
# user.comments
|
||||||
# => #<ActiveRecord::Associations::CollectionProxy>
|
# => #<ActiveRecord::Associations::CollectionProxy>
|
||||||
def strict_loading!(value = true)
|
def strict_loading!(value = true, mode: :all)
|
||||||
|
unless [:all, :n_plus_one_only].include?(mode)
|
||||||
|
raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only]."
|
||||||
|
end
|
||||||
|
|
||||||
@strict_loading = value
|
@strict_loading = value
|
||||||
|
@strict_loading_mode = mode
|
||||||
|
end
|
||||||
|
|
||||||
|
def strict_loading_n_plus_one_only? # :nodoc:
|
||||||
|
@strict_loading_mode == :n_plus_one_only
|
||||||
end
|
end
|
||||||
|
|
||||||
# Marks this record as read only.
|
# Marks this record as read only.
|
||||||
|
@ -830,6 +846,7 @@ module ActiveRecord
|
||||||
@destroyed_by_association = nil
|
@destroyed_by_association = nil
|
||||||
@_start_transaction_state = nil
|
@_start_transaction_state = nil
|
||||||
@strict_loading = self.class.strict_loading_by_default
|
@strict_loading = self.class.strict_loading_by_default
|
||||||
|
@strict_loading_mode = self.class.strict_loading_mode
|
||||||
|
|
||||||
self.class.define_attribute_methods
|
self.class.define_attribute_methods
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,17 +2,18 @@
|
||||||
|
|
||||||
require "cases/helper"
|
require "cases/helper"
|
||||||
require "models/developer"
|
require "models/developer"
|
||||||
|
require "models/company"
|
||||||
require "models/computer"
|
require "models/computer"
|
||||||
require "models/mentor"
|
require "models/mentor"
|
||||||
require "models/project"
|
require "models/project"
|
||||||
require "models/ship"
|
require "models/ship"
|
||||||
|
require "models/ship_part"
|
||||||
require "models/strict_zine"
|
require "models/strict_zine"
|
||||||
require "models/interest"
|
require "models/interest"
|
||||||
|
require "models/treasure"
|
||||||
|
|
||||||
class StrictLoadingTest < ActiveRecord::TestCase
|
class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
fixtures :developers
|
fixtures :developers, :developers_projects, :projects, :ships
|
||||||
fixtures :projects
|
|
||||||
fixtures :ships
|
|
||||||
|
|
||||||
def test_strict_loading!
|
def test_strict_loading!
|
||||||
developer = Developer.first
|
developer = Developer.first
|
||||||
|
@ -33,6 +34,45 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_strict_loading_n_plus_one_only_mode
|
||||||
|
developer = Developer.first
|
||||||
|
ship = Ship.first
|
||||||
|
ShipPart.create!(name: "Stern", ship: ship)
|
||||||
|
firm = Firm.create!("name" => "NASA")
|
||||||
|
project = Project.create!(name: "Apollo", firm: firm)
|
||||||
|
|
||||||
|
ship.update_column(:developer_id, developer.id)
|
||||||
|
developer.projects << project
|
||||||
|
developer.reload
|
||||||
|
|
||||||
|
developer.strict_loading!(mode: :n_plus_one_only)
|
||||||
|
assert_predicate developer, :strict_loading?
|
||||||
|
|
||||||
|
# Does not raise when loading a has_many association (:projects)
|
||||||
|
assert_nothing_raised do
|
||||||
|
developer.projects.to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
# strict_loading is enabled for has_many associations
|
||||||
|
assert developer.projects.all?(&:strict_loading?)
|
||||||
|
assert_raises ActiveRecord::StrictLoadingViolationError do
|
||||||
|
developer.projects.last.firm
|
||||||
|
end
|
||||||
|
|
||||||
|
# Does not raise when a belongs_to association (:ship) loads its
|
||||||
|
# has_many association (:parts)
|
||||||
|
assert_nothing_raised do
|
||||||
|
developer.ship.parts.to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
# strict_loading is enabled for has_many through a belongs_to
|
||||||
|
assert_not developer.ship.strict_loading?
|
||||||
|
assert developer.ship.parts.all?(&:strict_loading?)
|
||||||
|
assert_raises ActiveRecord::StrictLoadingViolationError do
|
||||||
|
developer.ship.parts.first.trinkets.to_a
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_strict_loading
|
def test_strict_loading
|
||||||
Developer.all.each { |d| assert_not d.strict_loading? }
|
Developer.all.each { |d| assert_not d.strict_loading? }
|
||||||
Developer.strict_loading.each { |d| assert d.strict_loading? }
|
Developer.strict_loading.each { |d| assert d.strict_loading? }
|
||||||
|
|
Loading…
Reference in a new issue