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

Bring back private class methods accessibility in named scope

The receiver in a scope was changed from `klass` to `relation` itself
for all scopes (named scope, default_scope, and association scope)
behaves consistently.

In addition. Before 5.2, if both an AR model class and a Relation
instance have same named methods (e.g. `arel_attribute`,
`predicate_builder`, etc), named scope doesn't respect relation instance
information.

For example:

```ruby
class Post < ActiveRecord::Base
  has_many :comments1, class_name: "RecentComment1"
  has_many :comments2, class_name: "RecentComment2"
end

class RecentComment1 < ActiveRecord::Base
  self.table_name = "comments"
  default_scope { where(arel_attribute(:created_at).gteq(2.weeks.ago)) }
end

class RecentComment2 < ActiveRecord::Base
  self.table_name = "comments"
  default_scope { recent_updated }
  scope :recent_updated, -> { where(arel_attribute(:updated_at).gteq(2.weeks.ago)) }
end
```

If eager loading `Post.eager_load(:comments1, :comments2).to_a`,
`:comments1` (default_scope) respects aliased table name, but
`:comments2` (using named scope) may not work correctly since named
scope doesn't respect relation instance information. See also 801ccab.

But this is a breaking change between releases without deprecation.
I decided to bring back private class methods accessibility in named
scope.

Fixes #31740.
Fixes #32331.
This commit is contained in:
Ryuta Kamizono 2018-03-27 19:31:34 +09:00
parent 1518457a67
commit b9be64cc3e
4 changed files with 19 additions and 2 deletions

View file

@ -313,6 +313,13 @@ module ActiveRecord
klass.current_scope = previous klass.current_scope = previous
end end
def _exec_scope(*args, &block) # :nodoc:
@delegate_to_klass = true
instance_exec(*args, &block) || self
ensure
@delegate_to_klass = false
end
# Updates all records in the current relation with details given. This method constructs a single SQL UPDATE # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
# statement and sends it straight to the database. It does not instantiate the involved models and it does not # statement and sends it straight to the database. It does not instantiate the involved models and it does not
# trigger Active Record callbacks or validations. However, values passed to #update_all will still go through # trigger Active Record callbacks or validations. However, values passed to #update_all will still go through

View file

@ -82,6 +82,9 @@ module ActiveRecord
if @klass.respond_to?(method) if @klass.respond_to?(method)
self.class.delegate_to_scoped_klass(method) self.class.delegate_to_scoped_klass(method)
scoping { @klass.public_send(method, *args, &block) } scoping { @klass.public_send(method, *args, &block) }
elsif defined?(@delegate_to_klass) &&
@delegate_to_klass && @klass.respond_to?(method, true)
@klass.send(method, *args, &block)
elsif arel.respond_to?(method) elsif arel.respond_to?(method)
ActiveSupport::Deprecation.warn \ ActiveSupport::Deprecation.warn \
"Delegating #{method} to arel is deprecated and will be removed in Rails 6.0." "Delegating #{method} to arel is deprecated and will be removed in Rails 6.0."

View file

@ -183,7 +183,7 @@ module ActiveRecord
if body.respond_to?(:to_proc) if body.respond_to?(:to_proc)
singleton_class.send(:define_method, name) do |*args| singleton_class.send(:define_method, name) do |*args|
scope = all scope = all
scope = scope.instance_exec(*args, &body) || scope scope = scope._exec_scope(*args, &body)
scope = scope.extending(extension) if extension scope = scope.extending(extension) if extension
scope scope
end end

View file

@ -12,9 +12,16 @@ class Topic < ActiveRecord::Base
scope :scope_with_lambda, lambda { all } scope :scope_with_lambda, lambda { all }
scope :by_lifo, -> { where(author_name: "lifo") } scope :by_lifo, -> { where(author_name: author_name) }
scope :replied, -> { where "replies_count > 0" } scope :replied, -> { where "replies_count > 0" }
class << self
private
def author_name
"lifo"
end
end
scope "approved_as_string", -> { where(approved: true) } scope "approved_as_string", -> { where(approved: true) }
scope :anonymous_extension, -> {} do scope :anonymous_extension, -> {} do
def one def one