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

Merge pull request #41825 from rails/refactor-scope-registry

Refactor scope registry
This commit is contained in:
Aaron Patterson 2021-04-08 08:53:36 -07:00 committed by GitHub
commit f95c0b7e96
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 60 additions and 41 deletions

View file

@ -422,18 +422,20 @@ module ActiveRecord
# Please check unscoped if you want to remove all previous scopes (including
# the default_scope) during the execution of a block.
def scoping(all_queries: nil)
if global_scope? && all_queries == false
registry = klass.scope_registry
if global_scope?(registry) && all_queries == false
raise ArgumentError, "Scoping is set to apply to all queries and cannot be unset in a nested block."
elsif already_in_scope?
elsif already_in_scope?(registry)
yield
else
_scoping(self, all_queries) { yield }
_scoping(self, registry, all_queries) { yield }
end
end
def _exec_scope(*args, &block) # :nodoc:
@delegate_to_klass = true
_scoping(nil) { instance_exec(*args, &block) || self }
registry = klass.scope_registry
_scoping(nil, registry) { instance_exec(*args, &block) || self }
ensure
@delegate_to_klass = false
end
@ -830,12 +832,12 @@ module ActiveRecord
end
private
def already_in_scope?
@delegate_to_klass && klass.current_scope(true)
def already_in_scope?(registry)
@delegate_to_klass && registry.current_scope(klass, true)
end
def global_scope?
klass.global_current_scope(true)
def global_scope?(registry)
registry.global_current_scope(klass, true)
end
def current_scope_restoring_block(&block)
@ -858,16 +860,19 @@ module ActiveRecord
klass.create!(attributes, &block)
end
def _scoping(scope, all_queries = false)
previous, klass.current_scope = klass.current_scope(true), scope
def _scoping(scope, registry, all_queries = false)
previous = registry.current_scope(klass, true)
registry.set_current_scope(klass, scope)
if all_queries
previous_global, klass.global_current_scope = klass.global_current_scope(true), scope
previous_global = registry.global_current_scope(klass, true)
registry.set_global_current_scope(klass, scope)
end
yield
ensure
klass.current_scope = previous
registry.set_current_scope(klass, previous)
if all_queries
klass.global_current_scope = previous_global
registry.set_global_current_scope(klass, previous_global)
end
end

View file

@ -8,7 +8,7 @@ module ActiveRecord
module SpawnMethods
# This is overridden by Associations::CollectionProxy
def spawn #:nodoc:
already_in_scope? ? klass.all : clone
already_in_scope?(klass.scope_registry) ? klass.all : clone
end
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.

View file

@ -24,19 +24,23 @@ module ActiveRecord
end
def current_scope(skip_inherited_scope = false)
ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
ScopeRegistry.current_scope(self, skip_inherited_scope)
end
def current_scope=(scope)
ScopeRegistry.set_value_for(:current_scope, self, scope)
ScopeRegistry.set_current_scope(self, scope)
end
def global_current_scope(skip_inherited_scope = false)
ScopeRegistry.value_for(:global_current_scope, self, skip_inherited_scope)
ScopeRegistry.global_current_scope(self, skip_inherited_scope)
end
def global_current_scope=(scope)
ScopeRegistry.set_value_for(:global_current_scope, self, scope)
ScopeRegistry.set_global_current_scope(self, scope)
end
def scope_registry
ScopeRegistry.instance
end
end
@ -80,17 +84,31 @@ module ActiveRecord
VALID_SCOPE_TYPES = [:current_scope, :ignore_default_scope, :global_current_scope]
def initialize
@registry = Hash.new { |hash, key| hash[key] = {} }
@current_scope = {}
@ignore_default_scope = {}
@global_current_scope = {}
end
VALID_SCOPE_TYPES.each do |type|
class_eval <<-eorb, __FILE__, __LINE__
def #{type}(model, skip_inherited_scope = false)
value_for(@#{type}, model, skip_inherited_scope)
end
def set_#{type}(model, value)
set_value_for(@#{type}, model, value)
end
eorb
end
private
# Obtains the value for a given +scope_type+ and +model+.
def value_for(scope_type, model, skip_inherited_scope = false)
raise_invalid_scope_type!(scope_type)
return @registry[scope_type][model.name] if skip_inherited_scope
return scope_type[model.name] if skip_inherited_scope
klass = model
base = model.base_class
while klass <= base
value = @registry[scope_type][klass.name]
value = scope_type[klass.name]
return value if value
klass = klass.superclass
end
@ -98,15 +116,7 @@ module ActiveRecord
# Sets the +value+ for a given +scope_type+ and +model+.
def set_value_for(scope_type, model, value)
raise_invalid_scope_type!(scope_type)
@registry[scope_type][model.name] = value
end
private
def raise_invalid_scope_type!(scope_type)
if !VALID_SCOPE_TYPES.include?(scope_type)
raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
end
scope_type[model.name] = value
end
end
end

View file

@ -173,11 +173,11 @@ module ActiveRecord
end
def ignore_default_scope?
ScopeRegistry.value_for(:ignore_default_scope, base_class)
ScopeRegistry.ignore_default_scope(base_class)
end
def ignore_default_scope=(ignore)
ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore)
ScopeRegistry.set_ignore_default_scope(base_class, ignore)
end
# The ignore_default_scope flag is used to prevent an infinite recursion

View file

@ -1259,9 +1259,9 @@ class BasicsTest < ActiveRecord::TestCase
UnloadablePost.unloadable
klass = UnloadablePost
assert_not_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, klass)
assert_not_nil ActiveRecord::Scoping::ScopeRegistry.current_scope(klass)
ActiveSupport::Dependencies.remove_unloadable_constants!
assert_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, klass)
assert_nil ActiveRecord::Scoping::ScopeRegistry.current_scope(klass)
ensure
Object.class_eval { remove_const :UnloadablePost } if defined?(UnloadablePost)
end

View file

@ -324,6 +324,10 @@ class FakeKlass
extend ActiveRecord::Delegation::DelegateCache
class << self
def scope_registry
ActiveRecord::Scoping::ScopeRegistry.instance
end
def connection
Post.connection
end