Как форсировать ассоциацию has_many, используя другой столбец в качестве внешнего ключа - PullRequest
0 голосов
/ 25 октября 2018

Застрял - на первый взгляд - простая проблема в 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.

Примечание. Включение self.primary_key = 'custom_id' в Foo будет работать частично.но я не думаю, что это хорошая идея.Я хочу сохранить foo_id в качестве первичного ключа

ОБНОВЛЕНИЕ:

Учитывая обратную связь - Спасибо вам, ребята, я загрузил этот пример здесь https://github.com/montenegrodr/temporary_repository_ror:

ОБНОВЛЕНИЕ № 2:

Ответы не удовлетворяют приведенному выше вопросу.Почему тест не проходит, я предполагаю, что он не должен провалиться.

ОБНОВЛЕНИЕ № 3:

Есть несколько новых ответов, которые мне все еще нужно оценить.Сделаем это в течение 24 часов.Спасибо.

ОБНОВЛЕНИЕ № 4:

Спасибо, ребята, за все ответы.Но ни один из них не удовлетворял критериям.Мне нужно пройти этот тест.Если это невозможно, может кто-нибудь объяснить, почему?Это ограничение рельсов?

Ответы [ 6 ]

0 голосов
/ 02 ноября 2018

Я оставлю вам полный файл, который вы можете сохранить и запустить с 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

Объяснение

Класс обоих Foo и Bar могут быть оба введены в ассоциации, поэтому вам не нужно указывать class_name в любом из них.

Если вы не включите primary_key, отношение будет использовать то, которое оноимеет по умолчанию: id.Вот почему ваш тест bar.foo_id == 1, потому что 1 - это id из Foo, поскольку по умолчанию primary_key

Помните, что столбец id создается в каждой таблицепо Rails, если вы явно не укажете.

Зная, какой столбец принадлежит к какой таблице в отношении, может быть довольно запутанным, я оставлю еще один пример с разными именами столбцов в каждой таблице для пояснения.Я также изменил названия моделей, чтобы лучше понять, какую роль играет каждая из них.

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

Добавление только правильных primary_key в обе ваши модели сделает ваш тест успешным.

0 голосов
/ 02 ноября 2018

Установите foreign_key и primary_key модели Bar на желаемое имя столбца (в данном случае custom_id).

class Bar < ApplicationRecord
  belongs_to :foo, foreign_key: "custom_id", primary_key: "custom_id"
end
0 голосов
/ 31 октября 2018

Я клонировал ваш репозиторий и запустил тестовый код, это был сбой

другие ответы на ваш вопрос верны, но вы написали неправильный тестовый код и добавили ненужный столбец в модель Foo

на самом деле вам нужно только добавить атрибут custom_id в модель 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

и для модели 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

на самом деле этоя не так написал код рельсов только для ответа на ваш вопрос

0 голосов
/ 30 октября 2018

Вам необходимо указать другой первичный ключ для отношения , если вы хотите достичь того, что вы хотите сделать.

Чтобы уточнить, это не то же самое, что изменение primary_key от модели.Этот способ изменяет только первичный ключ, используемый отношениями.Пожалуйста, смотрите нижнюю часть этого поста для примеров.

Я изменил ключи с обоих с помощью custom_id и изменил одну на foo_id.Таким образом, у вас есть лучшее представление о том, что происходит между моделями.Вы можете использовать оба custom_id, если хотите, но я бы посоветовал сохранить норму рельсов foo_id для ассоциации own_to.

Если вы хотите использовать оба из 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 определяется как foo через custom_id: 100, но foo все еще находится с ключом id: 1, а не с ключом custom_id.

0 голосов
/ 25 октября 2018

Хотя приведенный выше ответ верен, я добавляю некоторые объяснения к ответу.

Для правильной работы ассоциации has_many необходимо добавить foreign_key: :custom_id к Foo модели.Будет выполнен поиск в таблице столбцов записей с 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для правильной работы необходимо добавить модель foreign_key: :custom_id к Bar .Будет выполнен поиск в таблице foos и возвращена запись с id = custom_id Foo вместо 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]]
0 голосов
/ 25 октября 2018

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...