#база_данных #ruby_on_rails #activerecord
Столкнулся с такой проблемой.
У отелей есть много услуг (например трансфер, парковка, интернет и т.д.), так же
у каждой комнаты в отеле могут быть разные услуги (минибар, балкон, вид на море/парк
и т.д.).
Получается что объект (отель или комната) предоставляют услуги. Связь многие ко многим.
Таблица будет выглядеть примерно так (составной внешний ключ)
А как связать модели?
class Service < ActiveRecord::Base
has_many :service_in_object
has_many :hotels, through: :service_in_object
end
class ServiceInObject < ActiveRecord::Base
belongs_to :object #хмм
belongs_to :service
end
class Hotel < ActiveRecord::Base
has_many :rooms
has_many :service_in_object
has_many :service, through: :service_in_object
end
class Room < ActiveRecord::Base
belongs_to :hotel
has_many :service_in_object
has_many :service, through: :service_in_object
end
Как правильно это организовать?
Ответы
Ответ 1
У вас получается полиморфная связь, которую нужно ввести при помощи ключевого слова
polymorphic. Если не возражаете, я немного переделаю таблицу service_in_objects, чтобы
избавиться от object - не очень хорошее название, давайте сделаем serviceable. Миграции
для таблиц могут выглядеть следующим образом
create_table :hotels, comment: "Оттели" do |t|
t.string :title, comment: "Название"
end
create_table :rooms, comment: "Комнаты" do |t|
t.string :title, comment: "Номер"
t.integer :hotel_id, comment: "Внешний ключ для связи с оттелем"
end
create_table :services, comment: "Сервисы" do |t|
t.string :title, comment: "Название"
end
create_table :service_in_objects, comment: "Промежуточная cвязующая таблица" do |t|
t.integer :service_id, comment: "Внешний ключ для связи с сервисом"
t.integer :serviceable_id, comment: "Внешний ключ для связи с оттелем или комнатой"
t.string :serviceable_type, comment: "Внешний ключ для связи с оттелем или комнатой"
end
Тогда модели с учетом полиморфной связи через промежуточную таблицу service_in_objects
могут принять следующий вид
class Service < ActiveRecord::Base
has_many :service_in_objects
has_many \
:rooms,
through: :service_in_objects,
source: :serviceable,
source_type: 'Room'
has_many \
:hotels,
through: :service_in_objects,
source: :serviceable,
source_type: 'Hotel'
end
class ServiceInObject < ActiveRecord::Base
belongs_to :service
belongs_to :serviceable, polymorphic: true
end
class Hotel < ActiveRecord::Base
has_many :rooms
has_many :service_in_objects, as: :serviceable, dependent: :destroy
has_many :services, through: :service_in_objects
end
class Room < ActiveRecord::Base
belongs_to :hotel
has_many :service_in_objects, as: :serviceable, dependent: :destroy
has_many :services, through: :service_in_objects
end
Убедиться в том, что полиморфная связь работает, можно при помощи сидов (db/seed.rb):
ActiveRecord::Base.connection.execute('TRUNCATE hotels');
ActiveRecord::Base.connection.execute('TRUNCATE rooms');
ActiveRecord::Base.connection.execute('TRUNCATE services');
ActiveRecord::Base.connection.execute('TRUNCATE service_in_objects');
services = [{title: 'internet'}, {title: 'parking'}, {title: 'service1'}]
Service.create services
hotels = [{title: 'mariot'}, {title: 'hilton'}]
Hotel.create hotels
Hotel.all.each do |h|
h.rooms.create [{title: '1'}, {title: '2'}, {title: '4'}]
h.services << [Service.all.sample, Service.all.sample]
h.save
end
Room.all.each do |r|
r.services << [Service.all.sample, Service.all.sample]
r.save
end
В реальном проекте настоятельно рекомендуется покрыть тестами хотя бы связи - у вас
примере идущем с вопросом явные ошибки с единственным/множественным числом - тесты
вас сильно выручат на данном этапе. Напортачить в связях не сложно, модели будут работать
и со сломанными связями, только воспользоваться ими не получится и при этом сообщения
об ошибках на сломанных связях не совсем очевидны.
Обратите внимание:
Связь belongs_to - всегда единственное число, has_many - всегда множественное число.
Название таблиц - всегда множественное число, название моделей - всегда единственное
число.