Связи моделей. Один ко многим


(Oleg Fat) #1

Здравствуйте,
мне необходимо создать 3 модели, например: Organization, Employee, Phone и реализовать связь между ними таким образом, чтобы Organization и Employee имели связь один ко многим с Phone.
Я понимаю, что можно ручками набубенить ключи id для каждой из моделей и написать несколько методов, которые будут отслеживать поведение этих связей, но хочется сделать все правильно, через рельсовские механизмы.
Заранее спасибо!


(Мехоношин Алексей) #2

Привет!
Тут документация по связям в рельсах.
Средствами рельсов будет примерно так.

Создаём модели, миграции и мигрируем их:

rusdec:p []$ rails g model Organization name:string
rusdec:p []$ rails g model Employee name:string
rusdec:p []$ rails g model Phone number:string phonable:references{polymorphic}
rusdec:p []$ rails db:migrate
...

Так как phone-ом могут обладать разные сущности, я буду использовать полиморфную связь между таблицами.

Настройка моделей

Далее нужно прописать связи в файлах с моделями. В файле /app/model/phone.rb делать ничего не нужно, рельсы уже сгенерировали необходимый код обратной связи:

rusdec:p []$ cat app/models/phone.rb
class Phone < ApplicationRecord
  belongs_to :phonable, polymorphic: true
end

Осталось добавить следующую строку в модели Organization и Employee:

has_many :phones, as: :phonable, dependent: :destroy
rusdec:p []$ cat app/models/organization.rb
class Organization < ApplicationRecord
  has_many :phones, as: :phonable, dependent: :destroy
end
rusdec:p []$ cat app/models/employee.rb
class Employee < ApplicationRecord
 has_many :phones, as: :phonable, dependent: :destroy
end
  • has_many :phones - связь один-ко-многим (has_many),
  • as: :phonable - указываем, что мы полиморфны (будем соединяться к phonable_id)
  • dependent: :destroy - при удалении записи, удалять связанные с ней записи из таблицы phones

Проверка

Всё должно работать, проверим:

Создание организации
2.4.2 :001 > be = Organization.create(name: 'Be Inc')
...

Создание телефонов для организации
2.4.2 :002 > be.phones.create([{ phone_number: '+2348BE1UA' }, { phone_number: '+7983BE1RU' }])
2.4.2 :003 > be.phones.pluck(:phone_number)
(0.2ms)  SELECT "phones"."phone_number" FROM "phones" WHERE "phones"."phonable_id" = ? AND "phones"."phonable_type" = ?  [["phonable_id", 6], ["phonable_type", "Organization"]]
 => ["+2348BE1UA", "+7983BE1RU"]

Проверим, как телефон видит владельца
2.4.2 :007 > phone = Phone.last
2.4.2 :010 > phone.phonable.name
 => "Be Inc"

Удаление организации (связаные телефоны удаляются автоматически)
2.4.2 :004 > be.destroy
   (0.1ms)  begin transaction
  Phone Load (0.2ms)  SELECT "phones".* FROM "phones" WHERE "phones"."phonable_id" = ? AND "phones"."phonable_type" = ?  [["phonable_id", 6], ["phonable_type", "Organization"]]
  SQL (0.4ms)  DELETE FROM "phones" WHERE "phones"."id" = ?  [["id", 3]]
  SQL (0.1ms)  DELETE FROM "phones" WHERE "phones"."id" = ?  [["id", 4]]
  SQL (0.1ms)  DELETE FROM "organizations" WHERE "organizations"."id" = ?  [["id", 6]]
   (1.8ms)  commit transaction

Дополнение

Вместо того, чтобы прописывать has_many ... в каждую нужную модель, я бы использовал концерн.
Создаю файл концерна и вписываю туда следующий код:

rusdec:p []$ cat app/models/concerns/phonable.rb
module Phonable
  extend ActiveSupport::Concern

  included do
    has_many :phones, as: :phonable, dependent: :destroy
  end
end

Вписываю в нужные модели строку include Phonable:

rusdec:p []$ cat app/models/employee.rb
class Employee < ApplicationRecord
  include Phonable
end
rusdec:p []$ cat app/models/organization.rb
class Organization < ApplicationRecord
  include Phonable
end