mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #39491 from bogdanvlviv/strict_loading_by_default
Allow to enable/disable strict_loading mode by default for a model.
This commit is contained in:
commit
0267f18aca
4 changed files with 228 additions and 1 deletions
|
@ -1,3 +1,23 @@
|
||||||
|
* Add `ActiveRecord::Base.strict_loading_by_default` and `ActiveRecord::Base.strict_loading_by_default=`
|
||||||
|
to enable/disable strict_loading mode by default for a model. The configuration's value is
|
||||||
|
inheritable by subclasses, but they can override that value and it will not impact parent class.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
class Developer < ApplicationRecord
|
||||||
|
self.strict_loading_by_default = true
|
||||||
|
|
||||||
|
has_many :projects
|
||||||
|
end
|
||||||
|
|
||||||
|
dev = Developer.first
|
||||||
|
dev.projects.first
|
||||||
|
# => ActiveRecord::StrictLoadingViolationError Exception: Developer is marked as strict_loading and Project cannot be lazily loaded.
|
||||||
|
```
|
||||||
|
|
||||||
|
*bogdanvlviv*
|
||||||
|
|
||||||
* Deprecate passing an Active Record object to `quote`/`type_cast` directly.
|
* Deprecate passing an Active Record object to `quote`/`type_cast` directly.
|
||||||
|
|
||||||
*Ryuta Kamizono*
|
*Ryuta Kamizono*
|
||||||
|
|
|
@ -123,6 +123,8 @@ module ActiveRecord
|
||||||
|
|
||||||
class_attribute :belongs_to_required_by_default, instance_accessor: false
|
class_attribute :belongs_to_required_by_default, instance_accessor: false
|
||||||
|
|
||||||
|
class_attribute :strict_loading_by_default, instance_accessor: false, default: false
|
||||||
|
|
||||||
mattr_accessor :connection_handlers, instance_accessor: false, default: {}
|
mattr_accessor :connection_handlers, instance_accessor: false, default: {}
|
||||||
|
|
||||||
mattr_accessor :writing_role, instance_accessor: false, default: :writing
|
mattr_accessor :writing_role, instance_accessor: false, default: :writing
|
||||||
|
@ -600,7 +602,7 @@ module ActiveRecord
|
||||||
@marked_for_destruction = false
|
@marked_for_destruction = false
|
||||||
@destroyed_by_association = nil
|
@destroyed_by_association = nil
|
||||||
@_start_transaction_state = nil
|
@_start_transaction_state = nil
|
||||||
@strict_loading = false
|
@strict_loading = self.class.strict_loading_by_default
|
||||||
|
|
||||||
self.class.define_attribute_methods
|
self.class.define_attribute_methods
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,6 +29,43 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
Developer.strict_loading.each { |d| assert d.strict_loading? }
|
Developer.strict_loading.each { |d| assert d.strict_loading? }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_strict_loading_by_default
|
||||||
|
with_strict_loading_by_default(Developer) do
|
||||||
|
Developer.all.each { |d| assert d.strict_loading? }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_strict_loading_by_default_can_be_set_per_model
|
||||||
|
model1 = Class.new(ActiveRecord::Base) do
|
||||||
|
self.table_name = "developers"
|
||||||
|
self.strict_loading_by_default = true
|
||||||
|
end.new
|
||||||
|
|
||||||
|
model2 = Class.new(ActiveRecord::Base) do
|
||||||
|
self.table_name = "developers"
|
||||||
|
self.strict_loading_by_default = false
|
||||||
|
end.new
|
||||||
|
|
||||||
|
assert_predicate model1, :strict_loading?
|
||||||
|
assert_not_predicate model2, :strict_loading?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_strict_loading_by_default_is_inheritable
|
||||||
|
with_strict_loading_by_default(ActiveRecord::Base) do
|
||||||
|
model1 = Class.new(ActiveRecord::Base) do
|
||||||
|
self.table_name = "developers"
|
||||||
|
end.new
|
||||||
|
|
||||||
|
model2 = Class.new(ActiveRecord::Base) do
|
||||||
|
self.table_name = "developers"
|
||||||
|
self.strict_loading_by_default = false
|
||||||
|
end.new
|
||||||
|
|
||||||
|
assert_predicate model1, :strict_loading?
|
||||||
|
assert_not_predicate model2, :strict_loading?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_raises_if_strict_loading_and_lazy_loading
|
def test_raises_if_strict_loading_and_lazy_loading
|
||||||
dev = Developer.strict_loading.first
|
dev = Developer.strict_loading.first
|
||||||
assert_predicate dev, :strict_loading?
|
assert_predicate dev, :strict_loading?
|
||||||
|
@ -38,6 +75,17 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_raises_if_strict_loading_by_default_and_lazy_loading
|
||||||
|
with_strict_loading_by_default(Developer) do
|
||||||
|
dev = Developer.first
|
||||||
|
assert_predicate dev, :strict_loading?
|
||||||
|
|
||||||
|
assert_raises ActiveRecord::StrictLoadingViolationError do
|
||||||
|
dev.audit_logs.to_a
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_preload_audit_logs_are_strict_loading_because_parent_is_strict_loading
|
def test_preload_audit_logs_are_strict_loading_because_parent_is_strict_loading
|
||||||
developer = Developer.first
|
developer = Developer.first
|
||||||
|
|
||||||
|
@ -51,6 +99,21 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
assert dev.audit_logs.all?(&:strict_loading?), "Expected all audit logs to be strict_loading"
|
assert dev.audit_logs.all?(&:strict_loading?), "Expected all audit logs to be strict_loading"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preload_audit_logs_are_strict_loading_because_it_is_strict_loading_by_default
|
||||||
|
with_strict_loading_by_default(AuditLog) do
|
||||||
|
developer = Developer.first
|
||||||
|
|
||||||
|
3.times do
|
||||||
|
AuditLog.create(developer: developer, message: "I am message")
|
||||||
|
end
|
||||||
|
|
||||||
|
dev = Developer.includes(:audit_logs).first
|
||||||
|
|
||||||
|
assert_not_predicate dev, :strict_loading?
|
||||||
|
assert dev.audit_logs.all?(&:strict_loading?), "Expected all audit logs to be strict_loading"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_eager_load_audit_logs_are_strict_loading_because_parent_is_strict_loading_in_hm_relation
|
def test_eager_load_audit_logs_are_strict_loading_because_parent_is_strict_loading_in_hm_relation
|
||||||
developer = Developer.first
|
developer = Developer.first
|
||||||
|
|
||||||
|
@ -76,6 +139,22 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
assert dev.audit_logs.all?(&:strict_loading?), "Expected all audit logs to be strict_loading"
|
assert dev.audit_logs.all?(&:strict_loading?), "Expected all audit logs to be strict_loading"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_eager_load_audit_logs_are_strict_loading_because_it_is_strict_loading_by_default
|
||||||
|
with_strict_loading_by_default(AuditLog) do
|
||||||
|
developer = Developer.first
|
||||||
|
|
||||||
|
3.times do
|
||||||
|
AuditLog.create(developer: developer, message: "I am message")
|
||||||
|
end
|
||||||
|
|
||||||
|
dev = Developer.eager_load(:audit_logs).first
|
||||||
|
|
||||||
|
assert_not_predicate dev, :strict_loading?
|
||||||
|
assert_predicate AuditLog.last, :strict_loading?
|
||||||
|
assert dev.audit_logs.all?(&:strict_loading?), "Expected all audit logs to be strict_loading"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_raises_on_unloaded_relation_methods_if_strict_loading
|
def test_raises_on_unloaded_relation_methods_if_strict_loading
|
||||||
dev = Developer.strict_loading.first
|
dev = Developer.strict_loading.first
|
||||||
assert_predicate dev, :strict_loading?
|
assert_predicate dev, :strict_loading?
|
||||||
|
@ -85,6 +164,17 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_raises_on_unloaded_relation_methods_if_strict_loading_by_default
|
||||||
|
with_strict_loading_by_default(Developer) do
|
||||||
|
dev = Developer.first
|
||||||
|
assert_predicate dev, :strict_loading?
|
||||||
|
|
||||||
|
assert_raises ActiveRecord::StrictLoadingViolationError do
|
||||||
|
dev.audit_logs.first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_raises_on_lazy_loading_a_strict_loading_belongs_to_relation
|
def test_raises_on_lazy_loading_a_strict_loading_belongs_to_relation
|
||||||
mentor = Mentor.create!(name: "Mentor")
|
mentor = Mentor.create!(name: "Mentor")
|
||||||
|
|
||||||
|
@ -96,6 +186,19 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_raises_on_lazy_loading_a_belongs_to_relation_if_strict_loading_by_default
|
||||||
|
with_strict_loading_by_default(Developer) do
|
||||||
|
mentor = Mentor.create!(name: "Mentor")
|
||||||
|
|
||||||
|
developer = Developer.first
|
||||||
|
developer.update_column(:mentor_id, mentor.id)
|
||||||
|
|
||||||
|
assert_raises ActiveRecord::StrictLoadingViolationError do
|
||||||
|
developer.mentor
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_does_not_raise_on_eager_loading_a_strict_loading_belongs_to_relation
|
def test_does_not_raise_on_eager_loading_a_strict_loading_belongs_to_relation
|
||||||
mentor = Mentor.create!(name: "Mentor")
|
mentor = Mentor.create!(name: "Mentor")
|
||||||
|
|
||||||
|
@ -105,6 +208,17 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
assert_nothing_raised { developer.strict_loading_mentor }
|
assert_nothing_raised { developer.strict_loading_mentor }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_does_not_raise_on_eager_loading_a_belongs_to_relation_if_strict_loading_by_default
|
||||||
|
with_strict_loading_by_default(Developer) do
|
||||||
|
mentor = Mentor.create!(name: "Mentor")
|
||||||
|
|
||||||
|
Developer.first.update_column(:mentor_id, mentor.id)
|
||||||
|
developer = Developer.includes(:mentor).first
|
||||||
|
|
||||||
|
assert_nothing_raised { developer.mentor }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_raises_on_lazy_loading_a_strict_loading_has_one_relation
|
def test_raises_on_lazy_loading_a_strict_loading_has_one_relation
|
||||||
developer = Developer.first
|
developer = Developer.first
|
||||||
ship = Ship.first
|
ship = Ship.first
|
||||||
|
@ -116,6 +230,19 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_raises_on_lazy_loading_a_has_one_relation_if_strict_loading_by_default
|
||||||
|
with_strict_loading_by_default(Developer) do
|
||||||
|
developer = Developer.first
|
||||||
|
ship = Ship.first
|
||||||
|
|
||||||
|
ship.update_column(:developer_id, developer.id)
|
||||||
|
|
||||||
|
assert_raises ActiveRecord::StrictLoadingViolationError do
|
||||||
|
developer.ship
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_does_not_raise_on_eager_loading_a_strict_loading_has_one_relation
|
def test_does_not_raise_on_eager_loading_a_strict_loading_has_one_relation
|
||||||
Ship.first.update_column(:developer_id, Developer.first.id)
|
Ship.first.update_column(:developer_id, Developer.first.id)
|
||||||
developer = Developer.includes(:strict_loading_ship).first
|
developer = Developer.includes(:strict_loading_ship).first
|
||||||
|
@ -123,6 +250,15 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
assert_nothing_raised { developer.strict_loading_ship }
|
assert_nothing_raised { developer.strict_loading_ship }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_does_not_raise_on_eager_loading_a_has_one_relation_if_strict_loading_by_default
|
||||||
|
with_strict_loading_by_default(Developer) do
|
||||||
|
Ship.first.update_column(:developer_id, Developer.first.id)
|
||||||
|
developer = Developer.includes(:ship).first
|
||||||
|
|
||||||
|
assert_nothing_raised { developer.ship }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_raises_on_lazy_loading_a_strict_loading_has_many_relation
|
def test_raises_on_lazy_loading_a_strict_loading_has_many_relation
|
||||||
developer = Developer.first
|
developer = Developer.first
|
||||||
|
|
||||||
|
@ -137,6 +273,22 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_raises_on_lazy_loading_a_has_many_relation_if_strict_loading_by_default
|
||||||
|
with_strict_loading_by_default(Developer) do
|
||||||
|
developer = Developer.first
|
||||||
|
|
||||||
|
AuditLog.create(
|
||||||
|
3.times.map do
|
||||||
|
{ developer_id: developer.id, message: "I am message" }
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
assert_raises ActiveRecord::StrictLoadingViolationError do
|
||||||
|
developer.audit_logs.first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_does_not_raise_on_eager_loading_a_strict_loading_has_many_relation
|
def test_does_not_raise_on_eager_loading_a_strict_loading_has_many_relation
|
||||||
developer = Developer.first
|
developer = Developer.first
|
||||||
|
|
||||||
|
@ -151,6 +303,22 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
assert_nothing_raised { developer.strict_loading_opt_audit_logs.first }
|
assert_nothing_raised { developer.strict_loading_opt_audit_logs.first }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_does_not_raise_on_eager_loading_a_has_many_relation_if_strict_loading_by_default
|
||||||
|
with_strict_loading_by_default(Developer) do
|
||||||
|
developer = Developer.first
|
||||||
|
|
||||||
|
AuditLog.create(
|
||||||
|
3.times.map do
|
||||||
|
{ developer_id: developer.id, message: "I am message" }
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
developer = Developer.includes(:audit_logs).first
|
||||||
|
|
||||||
|
assert_nothing_raised { developer.audit_logs.first }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_raises_on_lazy_loading_a_strict_loading_habtm_relation
|
def test_raises_on_lazy_loading_a_strict_loading_habtm_relation
|
||||||
developer = Developer.first
|
developer = Developer.first
|
||||||
developer.projects << Project.first
|
developer.projects << Project.first
|
||||||
|
@ -162,10 +330,43 @@ class StrictLoadingTest < ActiveRecord::TestCase
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_raises_on_lazy_loading_a_habtm_relation_if_strict_loading_by_default
|
||||||
|
with_strict_loading_by_default(Developer) do
|
||||||
|
developer = Developer.first
|
||||||
|
developer.projects << Project.first
|
||||||
|
|
||||||
|
assert_not developer.projects.loaded?
|
||||||
|
|
||||||
|
assert_raises ActiveRecord::StrictLoadingViolationError do
|
||||||
|
developer.projects.first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_does_not_raise_on_eager_loading_a_strict_loading_habtm_relation
|
def test_does_not_raise_on_eager_loading_a_strict_loading_habtm_relation
|
||||||
Developer.first.projects << Project.first
|
Developer.first.projects << Project.first
|
||||||
developer = Developer.includes(:strict_loading_projects).first
|
developer = Developer.includes(:strict_loading_projects).first
|
||||||
|
|
||||||
assert_nothing_raised { developer.strict_loading_projects.first }
|
assert_nothing_raised { developer.strict_loading_projects.first }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_does_not_raise_on_eager_loading_a_habtm_relation_if_strict_loading_by_default
|
||||||
|
with_strict_loading_by_default(Developer) do
|
||||||
|
Developer.first.projects << Project.first
|
||||||
|
developer = Developer.includes(:projects).first
|
||||||
|
|
||||||
|
assert_nothing_raised { developer.projects.first }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def with_strict_loading_by_default(model)
|
||||||
|
previous_strict_loading_by_default = model.strict_loading_by_default
|
||||||
|
|
||||||
|
model.strict_loading_by_default = true
|
||||||
|
|
||||||
|
yield
|
||||||
|
ensure
|
||||||
|
model.strict_loading_by_default = previous_strict_loading_by_default
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -432,6 +432,10 @@ in controllers and views. This defaults to `false`.
|
||||||
controls whether a record fails validation if `belongs_to` association is not
|
controls whether a record fails validation if `belongs_to` association is not
|
||||||
present.
|
present.
|
||||||
|
|
||||||
|
* `config.active_record.strict_loading_by_default` is a boolean value
|
||||||
|
that either enables or disables strict_loading mode by default.
|
||||||
|
Defaults to `false`.
|
||||||
|
|
||||||
* `config.active_record.warn_on_records_fetched_greater_than` allows setting a
|
* `config.active_record.warn_on_records_fetched_greater_than` allows setting a
|
||||||
warning threshold for query result size. If the number of records returned
|
warning threshold for query result size. If the number of records returned
|
||||||
by a query exceeds the threshold, a warning is logged. This can be used to
|
by a query exceeds the threshold, a warning is logged. This can be used to
|
||||||
|
|
Loading…
Reference in a new issue