Helpex - Trao đổi & giúp đỡ Đăng nhập
9

Bị mắc kẹt trong một vấn đề - ngay từ cái nhìn đầu tiên - đơn giản trong RoR. Tôi chắc chắn rằng điều đó là dễ dàng, nhưng không có câu trả lời nào ở đây trong SO giúp tôi quá nhiều.

Tôi có hai mô hình ActiveRecord: Foocó nhiều Bars:

class Foo < ApplicationRecord
    has_many :bars
end

class Bar < ApplicationRecord
  belongs_to :foo
end

Điều đó hoạt động như một sự quyến rũ. Nhưng tôi muốn sử dụng một trường khác Foolà Foreign_key. Mặc định là foo_idtôi muốn sử dụng custom_idlàm khóa ngoại của mình. Vì vậy, tôi đã thử điều này (như nhiều giải pháp trên web đã đề xuất):

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

class Bars < ApplicationRecord
  belongs_to :foo, :class_name => 'Foo'
end

Nhưng điều đó không hiệu quả. tức là ActiveRecord tiếp tục ràng buộc Foođể Barssử dụng foo_id.

Lưu ý: Bao gồm self.primary_key = 'custom_id' trong Foo sẽ có tác dụng một phần. nhưng tôi không nghĩ đó là một ý kiến ​​hay. Tôi muốn giữ foo_id làm khóa chính

CẬP NHẬT:

Đưa ra phản hồi - Cảm ơn các bạn-, tôi đã tải lên ví dụ đó tại đây https://github.com/montenegrodr/tempional_repository_ror :

CẬP NHẬT # 2:

Các câu trả lời không thỏa mãn câu hỏi trên. Tại sao bài kiểm tra không thành công, giả định của tôi là nó không nên thất bại.

CẬP NHẬT # 3:

Có một số câu trả lời mới mà tôi vẫn cần đánh giá. Sẽ làm điều đó trong vòng 24 giờ. Cảm ơn.

CẬP NHẬT # 4:

Cảm ơn các bạn cho tất cả các câu trả lời. Nhưng không ai trong số họ thỏa mãn các tiêu chí. Tôi cần phải vượt qua bài kiểm tra đó. Nếu không được, ai đó có thể giải thích tại sao không? Nó có phải là một hạn chế đường ray?

9 hữu ích 1 bình luận 3.3k xem chia sẻ
9

Bạn cần chỉ định một khóa chính khác cho mối quan hệ nếu bạn muốn đạt được những gì bạn đang muốn làm.

Để làm rõ, điều này không giống như việc thay đổi primary_keymô hình. Cách này chỉ thay đổi khóa chính được sử dụng bởi mối quan hệ. Vui lòng xem phần dưới cùng của bài đăng này để biết các ví dụ.

Tôi đã thay đổi các phím từ cả hai sử dụng custom_idvà thay đổi một thành foo_id. Bằng cách này, bạn có một ý tưởng tốt hơn về những gì đang diễn ra giữa các mô hình. Bạn có thể sử dụng cả hai custom_idnếu bạn muốn, nhưng tôi khuyên bạn nên giữ tiêu chuẩn đường ray foo_idcho kết hợp thuộc_to.

Nếu bạn muốn sử dụng cả hai custom_id, bạn sẽ phải thêm một số foreign_keys


Dưới đây là các mô hình:

Foo

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

Quán ba

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

Cuộc di cư

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

Thanh tạo

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

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

Đây là Bài kiểm tra cập nhật hiện sẽ vượt qua:

Kiểm tra

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

Các ví dụ

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

Như bạn có thể thấy, phương thức này không thay đổi khóa chính của Foochính nó. Nó thay đổi khóa chính mối quan hệ giữa FooBarsử dụng. Thanh được xác thực để foo qua custom_id: 100, nhưng foo vẫn được tìm thấy với id: 1chìa khóa của nó , không phải custom_idchìa khóa của nó .

9 hữu ích 2 bình luận chia sẻ
4

Mặc dù câu trả lời trên là đúng, tôi đang thêm một số giải thích cho câu trả lời.

Để has_manyliên kết hoạt động bình thường, bạn cần thêm foreign_key: :custom_idvào mô hình Foo . Điều này sẽ tìm kiếm bảng thanh cho các bản ghi custom_id = idcủa Foo

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

Truy vấn cũ

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

Truy vấn mới

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

Để belongs_tohiệp hội hoạt động bình thường, bạn cũng cần thêm foreign_key: :custom_idvào mô hình Bar . Thao tác này sẽ tìm kiếm trong foosbảng và trả về bản ghi bằng id = custom_idFoo thay vìfoo_id

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

Truy vấn cũ

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

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

Truy vấn mới

SELECT  "foos".* FROM "foos" WHERE "foos"."id" = $1 [["id", 2]]
4 hữu ích 2 bình luận chia sẻ
3

Tôi sẽ để lại cho bạn một tệp hoàn chỉnh mà bạn có thể lưu và chạy với tệp ruby filename.rbđó sẽ hiển thị thẻ kiểm tra. (Mẫu cho bài kiểm tra này được lấy từ bug_report_templates của Rails )

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

Giải trình

Lớp của cả hai Foovà đều Barcó thể được đưa vào trong các liên kết, vì vậy bạn không cần phải chỉ định class_namemột trong hai.

Nếu bạn không bao gồm các primary_keymối quan hệ sẽ sử dụng một trong nó có theo mặc định: id. Đây là lý do tại sao thử nghiệm của bạn bar.foo_id == 1, vì 1idcủa Foo, là mặc địnhprimary_key

Hãy nhớ rằng cột idđược tạo trong mỗi bảng bởi Rails trừ khi bạn yêu cầu nó rõ ràng là không.

Biết cột hich thuộc bảng nào trong mối quan hệ có thể khá khó hiểu, tôi sẽ để lại một ví dụ khác với các tên cột khác nhau trên mỗi bảng để làm rõ hơn. Tôi cũng thay đổi tên người mẫu để hiểu rõ hơn về vai trò của từng người.

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

Chỉ thêm đúng primary_keytrong cả hai mô hình của bạn sẽ làm cho bài kiểm tra của bạn vượt qua.

3 hữu ích 0 bình luận chia sẻ
2

Đặt Foreign_keyprimary_key of Bar model thành tên cột mong muốn (custom_id trong trường hợp này).

class Bar < ApplicationRecord
  belongs_to :foo, foreign_key: "custom_id", primary_key: "custom_id"
end
2 hữu ích 0 bình luận chia sẻ
1

1 => Đưa ra nhu cầu triển khai custom_id: integertrong Barmô hình

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
1 hữu ích 3 bình luận chia sẻ
1

Tôi đã nhân bản kho lưu trữ của bạn và chạy mã thử nghiệm, nó không thành công

các câu trả lời khác cho câu hỏi của bạn là đúng, nhưng bạn đã viết sai mã kiểm tra và thêm cột không cần thiết trên mô hình Foo

thực sự bạn chỉ cần thêm thuộc tính custom_id vào mô hình Bar

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

và cho mô hình 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

và sau đó để kiểm tra mối quan hệ

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

thực ra đây không phải là cách tôi viết mã rails chỉ để trả lời câu hỏi của bạn

1 hữu ích 2 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ ruby-on-rails activerecord ruby-on-rails-5 , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading