Single Table Inheritance i asocjacja has_and_belongs_to_many dla dziecka

Cześć, korzystam z STI w taki sposób:

class Reservation < ApplicationRecord
end

class Reservation::Stay < Reservation
end

i mam do tego taką migrację:

class CreateReservations < ActiveRecord::Migration[5.1]
  def change
    create_table :reservations do |t|
      t.datetime :reservation_date
      t.integer :status, default: 0
      t.float :additional_costs
      t.string :type
      t.references :user, foreign_key: true

      t.timestamps
    end
  end
end

Chciałbym, żeby tylko Reservation::Stay czyli dziecko Reservation był w rejacji many-to-many z kolejnym modelem - Room. Nie potrzebuję modelu pośredniego, więc chcę zastosować has_many_and_belongs_to. Mam do tego taką migrację:

class CreateStayRooms < ActiveRecord::Migration[5.1]
  def change
    create_table :stay_rooms, id: false do |t|
      t.references :reservation, foreign_key: true, index: true
      t.references :room, foreign_key: true, index: true
    end
  end
end

zgodnie z dokumentacją wprowadziłem foregin_key and association_foregin_key w obu modelach:

class Reservation::Stay < Reservation
  has_and_belongs_to_many :rooms,
    foregin_key: 'room_id',  
    association_foregin_key: 'room_id',
    join_table: :stay_rooms
end

class Room < ApplicationRecord
  has_and_belongs_to_many :stays, 
    class_name: 'Reservation::Stay', 
    foregin_key: 'reservation_id', 
    association_foregin_key: 'reservation_id',
    join_table: :stay_rooms
  has_many :room_options
  has_many :options, through: :room_options
end

Niestety po utworzeniu Reservation::Stay i próbie wykonania Reservation::Stay.first.rooms dostaję taki błąd:

ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: stay_rooms.stay_id

Nie wiem w jaki sposób powiedzieć ActiveRecord, żeby używał reservation_id. Czy ktoś wie jak rozwiązać ten problem?

foregin_key => foreign_key

1 Like

To faktycznie pomogło nie zauważyłem literówki :smiley:, teraz to wygląda tak:

class Room < ApplicationRecord
  has_and_belongs_to_many :stays, 
    class_name: 'Reservation::Stay', 
    foreign_key: 'reservation_id',        
    association_foreign_key: 'reservation_id',
    join_table: :stays_rooms
  has_many :room_options
  has_many :options, through: :room_options
end

class Reservation::Stay < Reservation
  has_and_belongs_to_many :rooms,
    foreign_key: 'room_id',
    association_foreign_key: 'room_id',  
    join_table: :stays_rooms
end

i teraz fakycznie działa zapytanie Reservation::Stay.first.rooms, które zwraca pokoje, ale niestety w drugą stronę jest problem i zapytanie Room.first.stays zwraca pustą kolekcję, co jest nieprawdą.

SQL dla dwoch zapytań:
Reservation::Stay.first.rooms
SELECT "rooms".* FROM "rooms" INNER JOIN "stays_rooms" ON "rooms"."id" = "stays_rooms"."room_id" WHERE "stays_rooms"."room_id" = ? LIMIT ? [["room_id", 1], ["LIMIT", 11]]

Room.first.stays
SELECT "reservations".* FROM "reservations" INNER JOIN "stays_rooms" ON "reservations"."id" = "stays_rooms"."reservation_id" WHERE "stays_rooms"."reservation_id" = ? LIMIT ? [["reservation_id", 1], ["LIMIT", 11]]

foreign_key i association_foreign_key ustawiasz na ten sam klucz, a przecież w tabeli łączącej masz 2 różne klucze. Doczytaj w dokumentacji do czego służą te opcje i spróbuj z poniższym kodem:

class Room < ApplicationRecord
  has_and_belongs_to_many :stays,
    class_name: 'Reservation::Stay',
    foregin_key: 'room_id',
    association_foregin_key: 'reservation_id', # klucz drugiej tabeli (patrząc od rooms)
    join_table: :stay_rooms
  has_many :room_options
  has_many :options, through: :room_options
end

class Reservation::Stay < Reservation
  has_and_belongs_to_many :rooms,
    foreign_key: 'reservation_id',
    association_foregin_key: 'room_id', # klucz drugiej tabeli (patrząc od reservations)
    join_table: :stay_rooms
end

Dokumentacja:
http://guides.rubyonrails.org/association_basics.html#association-foreign-key
http://guides.rubyonrails.org/association_basics.html#options-for-has-and-belongs-to-many-foreign-key

1 Like

Wszystko działa dzięki!