mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Fixes Issue #13466.
Changed the call to a scope block to be evaluated with instance_eval. The result is that ScopeRegistry can use the actual class instead of base_class when caching scopes so queries made by classes with a common ancestor won't leak scopes.
This commit is contained in:
parent
9ed0cf51b4
commit
9c3afdc327
8 changed files with 42 additions and 5 deletions
|
@ -1,3 +1,14 @@
|
||||||
|
* Changed scoped blocks to be executed with `instance_eval`
|
||||||
|
|
||||||
|
Named scopes (i.e. using STI) were previously cached according to
|
||||||
|
base class so scoped queries made by classes with a common ancestor would
|
||||||
|
leak. Changed the way scope blocks were called so inheritance rules are
|
||||||
|
followed during the call and scopes are cached correctly.
|
||||||
|
|
||||||
|
Fixes #13466.
|
||||||
|
|
||||||
|
*Jefferson Lai*
|
||||||
|
|
||||||
* Save `has_one` association even if the record doesn't changed.
|
* Save `has_one` association even if the record doesn't changed.
|
||||||
|
|
||||||
Fixes #14407.
|
Fixes #14407.
|
||||||
|
|
|
@ -11,11 +11,11 @@ module ActiveRecord
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
def current_scope #:nodoc:
|
def current_scope #:nodoc:
|
||||||
ScopeRegistry.value_for(:current_scope, base_class.to_s)
|
ScopeRegistry.value_for(:current_scope, self.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_scope=(scope) #:nodoc:
|
def current_scope=(scope) #:nodoc:
|
||||||
ScopeRegistry.set_value_for(:current_scope, base_class.to_s, scope)
|
ScopeRegistry.set_value_for(:current_scope, self.to_s, scope)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -148,9 +148,13 @@ module ActiveRecord
|
||||||
extension = Module.new(&block) if block
|
extension = Module.new(&block) if block
|
||||||
|
|
||||||
singleton_class.send(:define_method, name) do |*args|
|
singleton_class.send(:define_method, name) do |*args|
|
||||||
scope = all.scoping { body.call(*args) }
|
if body.respond_to?(:to_proc)
|
||||||
|
scope = all.scoping { instance_exec(*args, &body) }
|
||||||
|
else
|
||||||
|
# Body is given as an object instead of a block, so invoke call()
|
||||||
|
scope = all.scoping { body.call(*args) }
|
||||||
|
end
|
||||||
scope = scope.extending(extension) if extension
|
scope = scope.extending(extension) if extension
|
||||||
|
|
||||||
scope || all
|
scope || all
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,12 +2,13 @@ require "cases/helper"
|
||||||
require 'models/post'
|
require 'models/post'
|
||||||
require 'models/topic'
|
require 'models/topic'
|
||||||
require 'models/comment'
|
require 'models/comment'
|
||||||
|
require 'models/rating'
|
||||||
require 'models/reply'
|
require 'models/reply'
|
||||||
require 'models/author'
|
require 'models/author'
|
||||||
require 'models/developer'
|
require 'models/developer'
|
||||||
|
|
||||||
class NamedScopingTest < ActiveRecord::TestCase
|
class NamedScopingTest < ActiveRecord::TestCase
|
||||||
fixtures :posts, :authors, :topics, :comments, :author_addresses
|
fixtures :posts, :authors, :topics, :comments, :author_addresses, :ratings
|
||||||
|
|
||||||
def test_implements_enumerable
|
def test_implements_enumerable
|
||||||
assert !Topic.all.empty?
|
assert !Topic.all.empty?
|
||||||
|
@ -115,6 +116,10 @@ class NamedScopingTest < ActiveRecord::TestCase
|
||||||
assert_equal 1,SpecialPost.containing_the_letter_a.count
|
assert_equal 1,SpecialPost.containing_the_letter_a.count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_scope_subquery_with_STI
|
||||||
|
assert_nothing_raised { VerySpecialComment.special_parent(SpecialRating.first).count }
|
||||||
|
end
|
||||||
|
|
||||||
def test_has_many_through_associations_have_access_to_scopes
|
def test_has_many_through_associations_have_access_to_scopes
|
||||||
assert_not_equal Comment.containing_the_letter_e, authors(:david).comments
|
assert_not_equal Comment.containing_the_letter_e, authors(:david).comments
|
||||||
assert !Comment.containing_the_letter_e.empty?
|
assert !Comment.containing_the_letter_e.empty?
|
||||||
|
|
10
activerecord/test/fixtures/ratings.yml
vendored
10
activerecord/test/fixtures/ratings.yml
vendored
|
@ -2,13 +2,23 @@ normal_comment_rating:
|
||||||
id: 1
|
id: 1
|
||||||
comment_id: 8
|
comment_id: 8
|
||||||
value: 1
|
value: 1
|
||||||
|
type: Rating
|
||||||
|
|
||||||
special_comment_rating:
|
special_comment_rating:
|
||||||
id: 2
|
id: 2
|
||||||
comment_id: 6
|
comment_id: 6
|
||||||
value: 1
|
value: 1
|
||||||
|
type: Rating
|
||||||
|
|
||||||
sub_special_comment_rating:
|
sub_special_comment_rating:
|
||||||
id: 3
|
id: 3
|
||||||
comment_id: 12
|
comment_id: 12
|
||||||
value: 1
|
value: 1
|
||||||
|
type: Rating
|
||||||
|
|
||||||
|
special_rating:
|
||||||
|
id: 4
|
||||||
|
comment_id: 10
|
||||||
|
value: 1
|
||||||
|
type: SpecialRating
|
||||||
|
special_comment_id: 3
|
||||||
|
|
|
@ -35,4 +35,5 @@ class SubSpecialComment < SpecialComment
|
||||||
end
|
end
|
||||||
|
|
||||||
class VerySpecialComment < Comment
|
class VerySpecialComment < Comment
|
||||||
|
scope :special_parent, -> (special_rating) { where parent_id: special_rating.special_comment.id }
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,3 +2,7 @@ class Rating < ActiveRecord::Base
|
||||||
belongs_to :comment
|
belongs_to :comment
|
||||||
has_many :taggings, :as => :taggable
|
has_many :taggings, :as => :taggable
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class SpecialRating < Rating
|
||||||
|
belongs_to :special_comment
|
||||||
|
end
|
||||||
|
|
|
@ -579,7 +579,9 @@ ActiveRecord::Schema.define do
|
||||||
|
|
||||||
create_table :ratings, force: true do |t|
|
create_table :ratings, force: true do |t|
|
||||||
t.integer :comment_id
|
t.integer :comment_id
|
||||||
|
t.integer :special_comment_id
|
||||||
t.integer :value
|
t.integer :value
|
||||||
|
t.string :type
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table :readers, force: true do |t|
|
create_table :readers, force: true do |t|
|
||||||
|
|
Loading…
Reference in a new issue