1
0
Fork 0
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:
Eileen M. Uchitelle 2020-06-01 08:33:33 -04:00 committed by GitHub
commit 0267f18aca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 228 additions and 1 deletions

View file

@ -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*

View file

@ -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

View file

@ -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

View file

@ -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