1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Allow reload to be default_scoped

`reload` is not `default_scoped` by default because you could be
creating a record that does not match your default scope and therefore
`reload` wouldn't find the record.

However, in the case of sharding an application you may want `reload` to
support `default_scope` because you'll always have the correct scope
set. To accomplish this I added a new method that checks if there are
any default scopes defined that are set to run for `all_queries`. If
there are then don't `unscope` the find methods.

If there is a case where you do want the default scope to apply to all
queries but be able to turn that off, I've added a `unscoped` option to
`reload`. This would use the original behavior regardless of whether the
`default_scope` was used for all queries.

Additionally, this exposes a new public method `default_scopes?` that
returns true if there are default scopes. If `all_queries` is passed to
`default_scopes?` the method will only return true if there is a default
scope for the model that has `all_queries` set to true. If there are no
default scopes with `all_queries` then it will return false.
This commit is contained in:
eileencodes 2020-12-11 13:18:45 -05:00
parent 3e5d504f78
commit a3343d2456
No known key found for this signature in database
GPG key ID: BA5C575120BBE8DF
3 changed files with 49 additions and 8 deletions

View file

@ -378,7 +378,7 @@ module ActiveRecord
def _update_record(values, constraints) # :nodoc:
constraints = _substitute_values(constraints).map { |attr, bind| attr.eq(bind) }
if default_scopes.any? && !default_scoped(all_queries: true).where_clause.empty?
if default_scopes?(all_queries: true)
constraints << default_scoped(all_queries: true).where_clause.ast
end
@ -392,7 +392,7 @@ module ActiveRecord
def _delete_record(constraints) # :nodoc:
constraints = _substitute_values(constraints).map { |attr, bind| attr.eq(bind) }
if default_scopes.any? && !default_scoped(all_queries: true).where_clause.empty?
if default_scopes?(all_queries: true)
constraints << default_scoped(all_queries: true).where_clause.ast
end
@ -808,12 +808,11 @@ module ActiveRecord
def reload(options = nil)
self.class.connection.clear_query_cache
fresh_object =
if options && options[:lock]
self.class.unscoped { self.class.lock(options[:lock]).find(id) }
else
self.class.unscoped { self.class.find(id) }
end
fresh_object = if self.class.default_scopes?(all_queries: true) && !(options && options[:unscoped])
find_record(options)
else
self.class.unscoped { find_record(options) }
end
@attributes = fresh_object.instance_variable_get(:@attributes)
@new_record = false
@ -873,6 +872,14 @@ module ActiveRecord
end
private
def find_record(options)
if options && options[:lock]
self.class.lock(options[:lock]).find(id)
else
self.class.find(id)
end
end
# A hook to be overridden by association modules.
def destroy_associations
end

View file

@ -52,6 +52,17 @@ module ActiveRecord
self.current_scope = nil
end
# Checks if the model has any default scopes. If all_queries
# is set to true, the method will check if there are any
# default_scopes for the model where `all_queries` is true.
def default_scopes?(all_queries: false)
if all_queries
self.default_scopes.map(&:all_queries).include?(true)
else
self.default_scopes.any?
end
end
private
# Use this macro in your model to set a default scope for all operations on
# the model.

View file

@ -158,6 +158,29 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_match(/mentor_id/, destroy_sql)
end
def test_default_scope_doesnt_run_on_reload
Mentor.create!
dev = DeveloperwithDefaultMentorScopeNot.create!(name: "Eileen")
reload_sql = capture_sql { dev.reload }.first
assert_no_match(/mentor_id/, reload_sql)
end
def test_default_scope_with_all_queries_runs_on_reload
Mentor.create!
dev = DeveloperWithDefaultMentorScopeAllQueries.create!(name: "Eileen")
reload_sql = capture_sql { dev.reload }.first
assert_match(/mentor_id/, reload_sql)
end
def test_default_scope_with_all_queries_doesnt_run_on_destroy_when_unscoped
dev = DeveloperWithDefaultMentorScopeAllQueries.create!(name: "Eileen", mentor_id: 2)
reload_sql = capture_sql { dev.reload({ unscoped: true }) }.first
assert_no_match(/mentor_id/, reload_sql)
end
def test_scope_overwrites_default
expected = Developer.all.merge!(order: "salary DESC, name DESC").to_a.collect(&:name)
received = DeveloperOrderedBySalary.by_name.to_a.collect(&:name)