如果多态关联的类型列不指向STI的基本模型,为什么多态关联不适用于STI?

Tru*_* Lê 41 activerecord ruby-on-rails polymorphic-associations

我有一个多态关联和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
Run Code Online (Sandbox Code Playgroud)

为了使多态关联起作用,根据多态关联的API文档,http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations 我必须设置borrowable_typebase_classSTI模型,在我的情况下是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
Run Code Online (Sandbox Code Playgroud)

但它似乎只有员工实例才是真的.结果是:

# 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>.
Run Code Online (Sandbox Code Playgroud)

谢谢

0x4*_*672 33

好问题.我使用Rails 3.1时遇到了完全相同的问题.看起来你不能这样做,因为它不起作用.可能这是一种预期的行为.显然,在Rails中结合使用多表关联和单表继承(STI)有点复杂.

Rails 3.2的当前Rails文档提供了结合多态关联和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
Run Code Online (Sandbox Code Playgroud)

  • 因此,这意味着您无法将Car与Guard关联并使用`@ guard.car`检索它,因为Car表将始终将其"borrowable_type"列设置为"Staff",而永远不会将其设置为"Guard".这意味着STI模型上的多态关联完全没用. (8认同)
  • @dekeguard您关于`@ guard.car`的陈述是错误的。使用提供的解决方案,Rails透明地将STI基类名“ Staff”用作目标类型,并且由于* id在STI表中始终是唯一的事实,因此将类型设置为`Guard`还是将其设置为在poly表中的“ Staff”将获取正确的记录。从@ car.guard出发也将返回各自的`Guard`。因此STI *对多态性很有用,因为STI模型不仅可以使用提供的解决方案工作,*其他模型仍然可以根据需要与多态性模型关联。 (2认同)

alk*_*fee 15

一个较老的问题,但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
Run Code Online (Sandbox Code Playgroud)

然后将其包含在您的模型中:

class Car < ActiveRecord::Base
  belongs_to :borrowable, :polymorphic => true
  include SingleTablePolymorphic
end
Run Code Online (Sandbox Code Playgroud)


Ric*_*eck 12

刚刚遇到这个问题Rails 4.2.我找到了两种解决方法:

-

问题是Rails使用base_classSTI关系的名称.

其他答案中记录了其原因,但要点是核心团队似乎觉得您应该能够引用而不是来进行多态STI关联.

我不同意这个想法,但我不是Rails核心团队的一员,所以没有太多的意见来解决它.

有两种方法可以解决它:

-

1)在模型级插入:

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
Run Code Online (Sandbox Code Playgroud)

这将{x}_type在将数据创建到db之前更改记录.这非常有效,并且仍然保留了关联的多态性.

2)覆盖核心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)
Run Code Online (Sandbox Code Playgroud)

解决问题的更系统的方法是编辑ActiveRecord管理它的核心方法.我在这个gem中使用了引用来找出需要修复/覆盖的元素.

这是未经测试的,仍然需要ActiveRecord核心方法的其他部分的扩展,但似乎适用于我的本地系统.


acr*_*oss 5

有一颗宝石. https://github.com/appfolio/store_base_sti_class

经过测试,适用于不同版本的AR.