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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用不同的列作为外键强制has_many关联相关的知识,希望对你有一定的参考价值。

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

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

class Foo < ApplicationRecord
    has_many :bars
end

class Bar < ApplicationRecord
  belongs_to :foo
end

这就像一个魅力。但我想使用另一个Foo字段作为foreign_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

但这不起作用。即ActiveRecord使用Foo保持Barsfoo_id的结合。

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

更新:

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

更新#2:

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

更新#3:

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

更新#4:

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

答案

如果您希望实现您想要的目标,则需要为关系指定不同的主键。

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

我使用custom_id更改了两个键并将其更改为foo_id。通过这种方式,您可以更好地了解模型之间的情况。如果你愿意,你可以同时使用两个custom_id,但我建议为belongs_to关联保留foo_id的rails规范。

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


以下是模型:

class Foo < ApplicationRecord
  has_many :bars,
           primary_key: :custom_id, 
           foreign_key: :foo_id
end

酒吧

class Bar < ApplicationRecord
  belongs_to :foo, 
             primary_key: :custom_id
end

迁移

CreateFoos

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

CreateBars

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

      t.integer :foo_id, index: true
      t.timestamps
    end
  end
end

这是现在应该传递的更新的测试:

测试

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

例子

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

如您所见,此方法不会更改Foo本身的主键。它改变了FooBar之间关系使用的主键。 Bar通过custom_id: 100与foo相关,但foo仍然发现它的id: 1键,而不是它的custom_id键。

另一答案

虽然上面的答案是正确的,但我在答案中加入了一些解释。

要让has_many协会正常工作,您需要将foreign_key: :custom_id添加到Foo模型中。这将在条形图表中搜索foo的custom_id = id记录

class Foo < ApplicationRecord
  has_many :bars, foreign_key: :custom_id
end

旧查询

SELECT "bars".* FROM "bars" WHERE "bars"."foo_id" = $1  [["foo_id", 1]]

新查询

SELECT "bars".* FROM "bars" WHERE "bars"."custom_id" = $1  [["custom_id", 1]]

要让belongs_to协会正常工作,您还需要将foreign_key: :custom_id添加到Bar模型中。这将搜索foos表并使用Qoo的id = custom_id而不是foo_id返回记录

class Bars < ApplicationRecord
  belongs_to :foo, foreign_key: :custom_id
end

旧查询

# <Bar id: 1, foo_id: 1, custom_id: 2, ...>

SELECT  "foos".* FROM "foos" WHERE "foos"."id" = $1 [["id", 1]]

新查询

SELECT  "foos".* FROM "foos" WHERE "foos"."id" = $1 [["id", 2]]
另一答案

我会留下一个完整的文件,你可以保存并运行ruby filename.rb,它将显示测试通过。 (此测试的模板取自Rails bug_report_templates

association_test.rb

# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  # Activate the gem you are reporting the issue against.
  gem "activerecord", "5.2.0"
  gem "sqlite3"
end

require "active_record"
require "minitest/autorun"
require "logger"

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :foos, force: true do |t|
    t.integer :custom_id, index: { unique: true }
    t.timestamps
  end

  create_table :bars, force: true do |t|
    t.integer :custom_id, index: true
    t.timestamps
  end
end

class Foo < ActiveRecord::Base
  has_many :bars, foreign_key: :custom_id, primary_key: :custom_id
end

class Bar < ActiveRecord::Base
  belongs_to :foo, foreign_key: :custom_id, primary_key: :custom_id
end

class BugTest < Minitest::Test
  def test_the_truth
    foo = Foo.new id: 1, custom_id: 100
    bar = Bar.new foo: foo
    assert foo.custom_id == bar.custom_id
  end
end

说明

qazxsw poi和qazxsw poi的类都可以在关联中推断出来,所以你不必在其中任何一个中指定Foo

如果你不包括Bar,那么关系将默认使用它:class_name。这就是为什么你的测试primary_key,因为idbar.foo_id == 11,是默认的id

请记住,除非您明确告诉它,否则Rails会在每个表中创建列Foo

知道hich列属于关系中的哪个表可能非常混乱,我将在每个表上留下另一个具有不同列名称的示例以进行说明。我还更改了模型名称,以便更好地了解每个角色扮演的角色。

primary_key

在你的两个模型中只添加正确的id将使ActiveRecord::Schema.define do create_table :classrooms, force: true do |t| t.integer :my_classroom_id, index: { unique: true } t.timestamps end create_table :students, force: true do |t| t.integer :student_c_id, index: true t.timestamps end end class Classroom < ActiveRecord::Base has_many :students, foreign_key: :student_c_id, primary_key: :my_classroom_id end class Student < ActiveRecord::Base belongs_to :classroom, foreign_key: :student_c_id, primary_key: :my_classroom_id end class BugTest < Minitest::Test def test_the_truth classroom = Classroom.new id: 1, my_classroom_id: 100 student = Student.new classroom: classroom assert student.student_c_id == classroom.my_classroom_id end end 通过。

另一答案

将Bar模型的primary_keyyour test设置为所需的列名称(在本例中为custom_id)。

foreign_key
另一答案

1 =>鉴于实施需要在primary_keymodel中使用class Bar < ApplicationRecord belongs_to :foo, foreign_key: "custom_id", primary_key: "custom_id" end

custom_id: integer
另一答案

我克隆了您的存储库并运行测试代码,但它失败了

你问题上的其他答案是正确的,但你写了错误的测试代码并在Foo模型上添加了不必要的列

实际上,您只需要向Bar模型添加custom_id属性

Bar

并为Foo模型

class Foo < ApplicationRecord
    has_many :bars, :class_name => "Bar", :foreign_key => "custom_id"
end

class Bar < ApplicationRecord
  belongs_to :foo, :class_name => "Foo", :foreign_key => "custom_id"
end

然后测试关系

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

      t.integer :custom_id, index: true
      t.timestamps
    end
  end
end

class Bar < ApplicationRecord
  belongs_to :foo, :class_name => "Foo", :foreign_key => "custom_id"
end

实际上,这不是我为了回答你的问题而编写rails代码的方式

以上是关于如何使用不同的列作为外键强制has_many关联的主要内容,如果未能解决你的问题,请参考以下文章

在休眠 ORM 中使用一个强制外键和一个可选外键中的列

如何在rails 5中为现有模型添加外键

Rails 4,通过连接表在has_many中指定自定义外键?

通过同一模型的两个 has_many 关系

如何在父应用程序模型和挂载引擎模型之间设置has_many关联?

使用 has_many 时如何创建关联模型的实例:通过关联