如何使用不同的列作为外键强制has_many关联

Mon*_*odr 6 activerecord ruby-on-rails ruby-on-rails-5

陷入困境 - 乍一看 - RoR中的简单问题.我相信这很容易,但是在SO中没有人回答太多帮助我.

我有两个ActiveRecord模型:Foo有很多Bars:

class Foo < ApplicationRecord
    has_many :bars
end

class Bar < ApplicationRecord
  belongs_to :foo
end
Run Code Online (Sandbox Code Playgroud)

这就像一个魅力.但我想使用另一个Fooforeign_key 字段.默认是foo_id我想custom_id用作我的外键.所以我尝试了这个(因为网上建议的解决方案很多):

class Foo < ApplicationRecord
    has_many :bars, :foreign_key => 'custom_id', :class_name => 'Bars'
end

class Bars < ApplicationRecord
  belongs_to :foo, :class_name => 'Foo'
end
Run Code Online (Sandbox Code Playgroud)

但这不起作用.即ActiveRecord的不断结合FooBars使用foo_id.

注意:在Foo中包含self.primary_key ='custom_id'可以部分工作.但我认为这不是一个好主意.我想保持foo_id为主键

更新:

鉴于反馈 - 谢谢你们 - 我在这里上传了这个例子https://github.com/montenegrodr/temporary_repository_ror:

更新#2:

答案不符合上述问题.为什么测试失败,我的假设是它不会失败.

更新#3:

我还需要评估一些新的答案.将在24小时内完成.谢谢.

更新#4:

谢谢你们所有答案.但他们都没有满足标准.我需要通过测试.如果不可能,有人可以解释原因吗?这是铁轨限制吗?

Kyl*_*iff 9

你需要指定一个不同的主键的关系,如果你想实现你在找什么做的。

澄清一下,这与更改primary_key模型的 不同。这种方式只是改变关系使用的主键。请参阅本文底部的示例。

我将密钥从 usingcustom_id更改为foo_id. 通过这种方式,您可以更好地了解模型之间发生的情况。custom_id如果您愿意,您可以同时使用两者,但我建议保留foo_idbelongs_to 关联的rails 规范。

如果您想同时使用 custom_id,则必须添加一些特定的 foreign_keys


以下是模型:

class Foo < ApplicationRecord
  has_many :bars,
           primary_key: :custom_id, 
           foreign_key: :foo_id
end
Run Code Online (Sandbox Code Playgroud)

酒吧

class Bar < ApplicationRecord
  belongs_to :foo, 
             primary_key: :custom_id
end
Run Code Online (Sandbox Code Playgroud)

迁移

创建Foos

class CreateFoos < ActiveRecord::Migration[5.2]
  def change
    create_table :foos do |t|

      t.integer :custom_id, index: {unique: true}
      t.timestamps
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

创建条

class CreateBars < ActiveRecord::Migration[5.2]
  def change
    create_table :bars do |t|

      t.integer :foo_id, index: true
      t.timestamps
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

这是现在应该通过的更新测试:

测试

require 'test_helper'

class BarTest < ActiveSupport::TestCase
  test "the truth" do
    foo = Foo.new(id: 1, custom_id: 100)
    bar = Bar.new(foo: foo)

    assert bar.foo_id == foo.custom_id
    # bar.foo_id    = 100
    # foo.custom_id = 100
  end
end
Run Code Online (Sandbox Code Playgroud)

例子

Foo.find(1) #<Foo id: 1, custom_id: 100>
Bar.first #<Bar id: 1, foo_id: 100>

Bar.first.foo = #<Foo id: 1, custom_id: 100>
Bar.first.foo == Foo.find(1) # true
Run Code Online (Sandbox Code Playgroud)

如您所见,此方法不会更改Foo自身的主键。它改变了主键FooBar使用之间的关系。Bar 是通过custom_id: 100foo关联到 foo 的,但是 foo 仍然可以通过它的id: 1键而不是它的custom_id键找到。