重新定义 Rails 中所有关联的外键名称

Redefine foreign key name for all associations in Rails

我有 一个 STI 模型有很多关联:

class MyModel < ActiveRecord::base
  has_many :things
  has_many :other_things
  # ... a lot of `has_many`
end

然后我将 非 STI 模型添加为嵌套只是为了向 MyModel 添加一些特定行为而不直接扩展它:

class Nested < MyModel
  self.inheritance_column = nil
end

但是我的联想不起作用。他们有 my_model_id 列,因为他们指的是 MyModel,他们也应该指的是 Nested。但是所有这些 has_many 都希望使用 nested_id 列作为外键(这取决于 class 名称)。

我可以在里面输入 class Nested:

has_many :things, foreign_key: 'my_model_id'
has_many :other_things, foreign_key: 'my_model_id'

但是如果可能的话,如何一次指定所有关联的外键 in Nested class?

如果您的所有 has_many 协会都在 MyModel 上声明,您应该没问题,或者您可能会受益于升级 Rails;以下内容在 4.2.4 中非常适合我。 Rails 使用声明 has_many 的 class 来生成 foreign_key,因此即使 Nested 继承,:things 仍然会被 my_model_id

class MyModel < ActiveRecord::Base
  has_many :things
end

class MyA < MyModel
end

class Nested < MyModel
  self.inheritance_column = nil
end

class Thing < ActiveRecord::Base
  belongs_to :my_model
end
> m = MyModel.create
 => #<MyModel id: 1, type: nil, created_at: "2015-12-22 02:21:16", updated_at: "2015-12-22 02:21:16">
> m.things.create
 => #<ActiveRecord::Associations::CollectionProxy [#<Thing id: 1, my_model_id: 1, created_at: "2015-12-22 02:21:19", updated_at: "2015-12-22 02:21:19">]>
> n = Nested.create
 => #<Nested id: 2, type: nil, created_at: "2015-12-22 02:21:27", updated_at: "2015-12-22 02:21:27">
> n.things.create
 => #<ActiveRecord::Associations::CollectionProxy [#<Thing id: 2, my_model_id: 2, created_at: "2015-12-22 02:21:32", updated_at: "2015-12-22 02:21:32">]>
> n.reload.things
  Nested Load (0.2ms)  SELECT  "my_models".* FROM "my_models" WHERE "my_models"."id" = ? LIMIT 1  [["id", 2]]
  Thing Load (0.1ms)  SELECT "things".* FROM "things" WHERE "things"."my_model_id" = ?  [["my_model_id", 2]]
 => #<ActiveRecord::Associations::CollectionProxy [#<Thing id: 2, my_model_id: 2, created_at: "2015-12-22 02:21:32", updated_at: "2015-12-22 02:21:32">]>

如果您给 Nested 自己的 has_many 关联,则生成外键的代码非常深且难以访问。您最终进入 HasManyReflection,它使用在您的 has_many 上声明的 foreign_key(或 as)选项或从 class 名称派生。没有明显的方法来自定义这些方法,除非你做一些不可取的事情,比如 override Nested.name.

foreign_key:

def foreign_key
  @foreign_key ||= options[:foreign_key] || derive_foreign_key
end

derive_foreign_key:

def derive_foreign_key
  if belongs_to?
    "#{name}_id"
  elsif options[:as]
    "#{options[:as]}_id"
  else
    active_record.name.foreign_key
  end
end

所以最简单的方法可能就是一个循环:

class Nested < MyModel
  self.inheritance_column = nil

  %i[things other_things].each do |association|
    has_many association, foreign_key: 'my_model_id'
  end
end

或者如果你觉得元编程,重新定义 has_many:

class Nested < MyModel
  self.inheritance_column = nil

  def self.has_many(klass, options={})
    options.reverse_merge!(foreign_key: 'my_model_id')
    super
  end

  has_many :things
  has_many :other_things
end

我的解决方案可能不被推荐,但它有效。这是:

class Nested < MyModel
  def self.name
    MyModel.name
  end
end

ActiveRecord 将为 NestedMyModel 类.[=15= 中定义或重新定义的所有关联寻找 my_model_id 外键]