对RoR中的一个简单问题一见钟情。我确信这很容易,但是在这里没有答案对我有太大帮助。
我有两个ActiveRecord模型:value
有很多for in
:
Foo
那就像一种魅力。但是我想使用Bars
的另一个字段作为foreign_key。默认值为class Foo < ApplicationRecord
has_many :bars
end
class Bar < ApplicationRecord
belongs_to :foo
end
,我想使用Foo
作为我的外键。因此,我尝试了此操作(根据网络上的许多解决方案建议):
foo_id
但这不起作用。即ActiveRecord一直使用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
绑定到Foo
。
注意:在Foo中包含self.primary_key ='custom_id'将部分起作用。但我认为这不是一个好主意。我想保留foo_id作为主键
更新:
鉴于反馈-谢谢大家-,我在https://github.com/montenegrodr/temporary_repository_ror此处上传了该示例:
更新#2:
答案不满足上述问题。为什么测试失败了,我的假设是测试不应该失败。
更新#3:
我仍然需要评估几个新答案。将在24小时内完成。谢谢。
更新#4:
谢谢你们的所有答案。但是他们都没有满足标准。我需要通过测试。如果不可能的话,有人可以解释为什么吗?这是一个约束吗?
答案 0 :(得分:4)
虽然以上答案是正确的,但我正在为答案添加一些解释。
要使has_many
关联正常工作,您需要向{strong> Foo 模型添加foreign_key: :custom_id
。这将在bar表中搜索custom_id = id
为Foo的记录
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
关联正常运行,您还需要向{strong> Bar 模型添加foreign_key: :custom_id
。这将搜索foos
表,并以Foo的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]]
答案 1 :(得分:3)
如果您想实现自己的目标,则需要为关系指定其他主键。
为澄清起见,这与更改模型的primary_key
不同。这种方式仅更改关系使用的主键。请查看这篇文章的底部以获取示例。
我同时使用custom_id
更改了密钥,并将其中一个更改为foo_id
。这样,您可以更好地了解模型之间的情况。如果愿意,可以同时使用两个custom_id
,但是我建议对belongs_to关联保持foo_id
的规范。
如果要同时使用两个custom_id,则必须添加一些特定的foreign_keys
Foo
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,但是仍然可以通过其id: 1
键而不是其custom_id
键找到foo。
答案 2 :(得分:3)
我将给您留下一个完整的文件,您可以使用ruby filename.rb
保存并运行该文件,其中将显示测试合格。 (此测试的模板来自Rails bug_report_templates)
# 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
Foo
和Bar
的类都可以在关联中进行推断,因此您不必在任何一个中都指定class_name
。
如果不包括primary_key
,则该关系将使用默认情况下具有的关系:id
。这就是您的测试bar.foo_id == 1
的原因,因为1
是id
的{{1}},是默认的Foo
请记住,除非您明确告诉列
primary_key
是由Rails在每个表中创建的。
知道hich列属于该关系中的哪个表可能会引起混淆,我将在每个表上留下另一个示例以说明不同的列名。我还更改了模型名称,以更好地了解哪个角色正在发挥作用。
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
即可通过your test。
答案 3 :(得分:2)
将Bar模型的foreign_key和primary_key设置为所需的列名(在这种情况下为custom_id)。
class Bar < ApplicationRecord
belongs_to :foo, foreign_key: "custom_id", primary_key: "custom_id"
end
答案 4 :(得分:1)
1 =>在custom_id: integer
模型中,给定实现需要Bar
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
答案 5 :(得分:1)
我克隆了您的存储库并运行测试代码,但失败了
您的问题的其他答案是正确的,但是您编写了错误的测试代码,并在Foo模型上添加了不必要的列
实际上您只需要向bar模型添加custom_id属性
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
对于Foo模型
class CreateFoos < ActiveRecord::Migration[5.2]
def change
create_table :foos do |t|
t.timestamps
end
end
end
class Foo < ApplicationRecord
has_many :bars, :class_name => "Bar", :foreign_key => "custom_id"
end
然后测试该关系
require 'test_helper'
class BarTest < ActiveSupport::TestCase
test 'the truth' do
foo = Foo.new
foo.save!
bar = Bar.new(foo: foo)
bar.save!
assert foo.bar_ids.include?(bar.id)
assert bar.foo_id == foo.id
end
end
实际上这不是我编写Rails代码的方式 仅用于回答您的问题