我有一个多态关联和STI的案例。
# app/models/car.rb
class Car < ActiveRecord::Base
belongs_to :borrowable, :polymorphic => true
end
# app/models/staff.rb
class Staff < ActiveRecord::Base
has_one :car, :as => :borrowable, :dependent => :destroy
end
# app/models/guard.rb
class Guard < Staff
end
为了使多态关联起作用,根据多态关联的API文档,http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations,我必须将borrowable_type
设置为STI模型的base_class
,即我的情况是Staff
。
问题是:如果borrowable_type
设置为STI类,为什么不起作用?
进行一些测试以证明它:
# now the test speaks only truth
# test/fixtures/cars.yml
one:
name: Enzo
borrowable: staff (Staff)
two:
name: Mustang
borrowable: guard (Guard)
# test/fixtures/staffs.yml
staff:
name: Jullia Gillard
guard:
name: Joni Bravo
type: Guard
# test/units/car_test.rb
require 'test_helper'
class CarTest < ActiveSupport::TestCase
setup do
@staff = staffs(:staff)
@guard = staffs(:guard)
end
test "should be destroyed if an associated staff is destroyed" do
assert_difference('Car.count', -1) do
@staff.destroy
end
end
test "should be destroyed if an associated guard is destroyed" do
assert_difference('Car.count', -1) do
@guard.destroy
end
end
end
但只有员工实例似乎才是真的。结果是:
# Running tests:
F.
Finished tests in 0.146657s, 13.6373 tests/s, 13.6373 assertions/s.
1) Failure:
test_should_be_destroyed_if_an_associated_guard_is_destroyed(CarTest) [/private/tmp/guineapig/test/unit/car_test.rb:16]:
"Car.count" didn't change by -1.
<1> expected but was
<2>.
由于
答案 0 :(得分:31)
好问题。我使用Rails 3.1时遇到了完全相同的问题。看起来你不能这样做,因为它不起作用。可能这是一种预期的行为。显然,在Rails中结合使用多表关联和单表继承(STI)有点复杂。
Rails 3.2的当前Rails文档提供了组合polymorphic associations and STI:
的建议将多态关联与单个表结合使用 继承(STI)有点棘手。为了协会 按预期工作,确保存储STI的基本模型 多态关联的类型列中的模型。
在您的情况下,基本模型将是“Staff”,即“borrowable_type”应为所有项目的“Staff”,而不是“Guard”。通过使用“变成”:guard.becomes(Staff)
,可以使派生类显示为基类。可以将“borrowable_type”列直接设置为基类“Staff”,或者如Rails文档建议的那样,使用
class Car < ActiveRecord::Base
..
def borrowable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
答案 1 :(得分:14)
一个较旧的问题,但Rails 4中的问题仍然存在。另一种选择是动态地创建/覆盖_type
方法。如果您的应用程序使用与STI的多个多态关联并且您希望将逻辑保留在一个位置,这将非常有用。
此问题将抓取所有多态关联,并确保始终使用基类保存记录。
# models/concerns/single_table_polymorphic.rb
module SingleTablePolymorphic
extend ActiveSupport::Concern
included do
self.reflect_on_all_associations.select{|a| a.options[:polymorphic]}.map(&:name).each do |name|
define_method "#{name.to_s}_type=" do |class_name|
super(class_name.constantize.base_class.name)
end
end
end
end
然后将其包含在您的模型中:
class Car < ActiveRecord::Base
belongs_to :borrowable, :polymorphic => true
include SingleTablePolymorphic
end
答案 2 :(得分:11)
在Rails 4.2
中遇到过这个问题。我找到了两种解决方法:
-
问题是Rails使用STI关系的base_class
名称。
其他答案中记录了其原因,但要点是核心团队似乎认为您应该能够引用表而不是类< / em>用于多态STI关联。
我不同意这个想法,但我不是Rails核心团队的一员,所以没有太多的意见来解决它。
有两种方法可以解决它:
-
class Association < ActiveRecord::Base
belongs_to :associatiable, polymorphic: true
belongs_to :associated, polymorphic: true
before_validation :set_type
def set_type
self.associated_type = associated.class.name
end
end
这会在将数据创建到db之前更改{x}_type
记录。这非常有效,并且仍然保留了关联的多态性。
ActiveRecord
方法#app/config/initializers/sti_base.rb
require "active_record"
require "active_record_extension"
ActiveRecord::Base.store_base_sti_class = false
#lib/active_record_extension.rb
module ActiveRecordExtension #-> http://stackoverflow.com/questions/2328984/rails-extending-activerecordbase
extend ActiveSupport::Concern
included do
class_attribute :store_base_sti_class
self.store_base_sti_class = true
end
end
# include the extension
ActiveRecord::Base.send(:include, ActiveRecordExtension)
####
module AddPolymorphic
extend ActiveSupport::Concern
included do #-> http://stackoverflow.com/questions/28214874/overriding-methods-in-an-activesupportconcern-module-which-are-defined-by-a-cl
define_method :replace_keys do |record=nil|
super(record)
owner[reflection.foreign_type] = ActiveRecord::Base.store_base_sti_class ? record.class.base_class.name : record.class.name
end
end
end
ActiveRecord::Associations::BelongsToPolymorphicAssociation.send(:include, AddPolymorphic)
解决问题的更系统的方法是编辑管理它的ActiveRecord
核心方法。我在this gem中使用了引用来找出需要修复/覆盖的元素。
这是未经测试的,仍然需要ActiveRecord核心方法的其他部分的扩展,但似乎适用于我的本地系统。
答案 3 :(得分:5)
有一颗宝石。 https://github.com/appfolio/store_base_sti_class
经过测试,适用于不同版本的AR。
答案 4 :(得分:1)
这就是我使用上述提示解决该问题的方式:
# app/models/concerns/belongs_to_single_table_polymorphic.rb
module BelongsToSingleTablePolymorphic
extend ActiveSupport::Concern
included do
def self.belongs_to_sti_polymorphic(model)
class_eval "belongs_to :#{model}, polymorphic: true"
class_eval 'before_validation :set_sti_object_type'
define_method('set_sti_object_type') do
sti_type = send(model).class.name
send("#{model}_type=", sti_type)
end
end
end
end
,对于任何我能找到belongs_to :whatever, polymorphic: true
的模型,我都会这样做:
class Reservation < ActiveRecord::Base
include BelongsToSingleTablePolymorphic
# .....
belongs_to_sti_polymorphic :whatever
# .....
end
答案 5 :(得分:0)
我同意一般意见,认为这应该更容易。也就是说,这对我有用。
我有一个模型,其中Firm作为基类,Customer和Prospect作为STI类,如下所示:
class Firm
end
class Customer < Firm
end
class Prospect < Firm
end
我还有一个多态类,机会,看起来像这样:
class Opportunity
belongs_to :opportunistic, polymorphic: true
end
我想将机会称为
customer.opportunities
或
prospect.opportunities
为此,我按如下方式更改了模型。
class Firm
has_many opportunities, as: :opportunistic
end
class Opportunity
belongs_to :customer, class_name: 'Firm', foreign_key: :opportunistic_id
belongs_to :prospect, class_name: 'Firm', foreign_key: :opportunistic_id
end
我通过机会主义类型的“公司”来节省机会。 (基类)和相应的客户或潜在客户ID作为opportunistic_id。
现在我可以完全按照自己的意愿获得customer.opportunities和prospect.opportunities。
答案 6 :(得分:0)
您还可以为多态类型的has_*
关联建立自定义范围:
class Staff < ActiveRecord::Base
has_one :car,
->(s) { where(cars: { borrowable_type: s.class }, # defaults to base_class
foreign_key: :borrowable_id,
:dependent => :destroy
end
由于多态联接使用复合外键(* _id和* _type),因此需要使用正确的值指定type子句。 _id只能与foreign_key
声明一起使用,该声明指定多态关联的名称。
由于多态性的性质,知道什么模型是可借用的模型可能令人沮丧,因为可以想象它可能是Rails应用程序中的任何模型。在您希望对可借项实施级联删除的任何模型中,都需要声明此关系。