提交 51660f01 编写于 作者: E eileencodes

Fix leaky chain on polymorphic association

If there was a polymorphic hm:t association with a scope AND second
non-scoped hm:t association on a model the polymorphic scope would leak
through into the call for the non-polymorhic hm:t association.

This would only break if `hotel.drink_designers` was called before
`hotel.recipes`. If `hotel.recipes` was called first there would be
no problem with the SQL.

Before (employable_type should not be here):
```
SELECT COUNT(*) FROM "drink_designers" INNER JOIN "chefs" ON
"drink_designers"."id" = "chefs"."employable_id" INNER JOIN
"departments" ON "chefs"."department_id" = "departments"."id" WHERE
"departments"."hotel_id" = ? AND "chefs"."employable_type" = ?
[["hotel_id", 1], ["employable_type", "DrinkDesigner"]]
```

After:
```
SELECT COUNT(*) FROM "recipes" INNER JOIN "chefs" ON "recipes"."chef_id"
= "chefs"."id" INNER JOIN "departments" ON "chefs"."department_id" =
"departments"."id" WHERE "departments"."hotel_id" = ?  [["hotel_id", 1]]
```

From the SQL you can see that `employable_type` was leaking through when
calling recipes. The solution is to dup the chain of the polymorphic
association so it doesn't get cached. Additionally, this follows
`scope_chain` which dup's the `source_reflection`'s `scope_chain`.

This required another model/table/relationship because the leak only
happens on a hm:t polymorphic that's called before another hm:t on the
same model.

I am specifically testing the SQL here instead of the number of records
becasue the test could pass if there was 1 drink designer recipe for the
drink designer chef even though the `employable_type` was leaking through.
This needs to specifically check that `employable_type` is not in the SQL
statement.
上级 b6c038b6
......@@ -694,7 +694,7 @@ def through_reflection
def chain
@chain ||= begin
a = source_reflection.chain
b = through_reflection.chain
b = through_reflection.chain.map(&:dup)
if options[:source_type]
b[0] = PolymorphicReflection.new(b[0], self)
......
......@@ -23,6 +23,7 @@
require 'models/department'
require 'models/cake_designer'
require 'models/drink_designer'
require 'models/recipe'
class ReflectionTest < ActiveRecord::TestCase
include ActiveRecord::Reflection
......@@ -277,6 +278,16 @@ def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case
assert_equal 2, @hotel.chefs.size
end
def test_scope_chain_of_polymorphic_association_does_not_leak_into_other_hmt_associations
hotel = Hotel.create!
department = hotel.departments.create!
drink = department.chefs.create!(employable: DrinkDesigner.create!)
recipe = Recipe.create!(chef_id: drink.id, hotel_id: hotel.id)
hotel.drink_designers.to_a
assert_sql(/^(?!.*employable_type).*$/) { hotel.recipes.to_a }
end
def test_nested?
assert !Author.reflect_on_association(:comments).nested?
assert Author.reflect_on_association(:tags).nested?
......
class Chef < ActiveRecord::Base
belongs_to :employable, polymorphic: true
has_many :recipes
end
......@@ -3,4 +3,5 @@ class Hotel < ActiveRecord::Base
has_many :chefs, through: :departments
has_many :cake_designers, source_type: 'CakeDesigner', source: :employable, through: :chefs
has_many :drink_designers, source_type: 'DrinkDesigner', source: :employable, through: :chefs
has_many :recipes, through: :chefs
end
class Recipe < ActiveRecord::Base
belongs_to :chef
end
......@@ -886,6 +886,10 @@ def except(adapter_names_to_exclude)
t.string :employable_type
t.integer :department_id
end
create_table :recipes, force: true do |t|
t.integer :chef_id
t.integer :hotel_id
end
create_table :records, force: true do |t|
end
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册