如何使用不同的列作为外键强制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
保持Bars
与foo_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
本身的主键。它改变了Foo
和Bar
之间关系使用的主键。 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
,因为id
是bar.foo_id == 1
的1
,是默认的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_key
和your 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关联的主要内容,如果未能解决你的问题,请参考以下文章
Rails 4,通过连接表在has_many中指定自定义外键?