Reputation: 5908
Imagine a travel website where you have HotelOwner
s and Tourist
s. When they start a conversation, the app creates a join model named Conversation
using has_many through. It's a classic many to many association:
class HotelOwner
has_many :tourists, through: :conversations
has_many :conversations
end
class Tourist
has_many :hotel_owners, through: :conversations
has_many :conversations
end
class Conversation
belongs_to :hotel_owner
belongs_to :tourist
end
Now we can use hotel_owner.tourists
and tourist.hotel_owners
. Also, the join model Conversation
is also being used to keep some state on that association between them both (like, HotelOwner
comments on Tourist
and vice-versa).
But now we need a Reservation model. My initial ideia was this:
class Reservation
belongs_to :hotel_owner
belongs_to :tourist
end
But we also need to create the Conversation
join model, since app logic requires that there cannot be a Reservation
without a previous Conversation
, even if a blank one. Also, the hotel_owner notes on tourist and vice-versa should be kept there and need to exist if a reservation exists.
After thinking about using manual callbacks to manually create the join model Conversation
, I read that it would not be a good idea to add a belongs_to :conversation
on Reservation
because it could lead to database inconsistencies (like the problem if reservation.conversation.tourist
pointed to a different tourist then reservation.tourist
.. there should be a single source of truth to this association right?)
I then had the idea of using Conversation
as a proxy to Reservations, like this:
class HotelOwner
has_many :tourists, through: :conversations
has_many :conversations
has_many :reservations, through: :conversations
end
class Tourist
has_many :hotel_owners, through: :conversations
has_many :conversations
has_many :reservations, through: :conversations
end
class Conversation
belongs_to :hotel_owner
belongs_to :tourist
has_many :reservations
end
class Reservation
has_one :hotel_owner, through: :conversation
has_one :tourist, through: :conversation
belongs_to :conversation
end
Since there is no belongs_to through
in Rails to use in Reservation
, other posts in SO suggest using has_one trough
instead, just like I did above.
The problem is that conversation has_many reservations, and does not belong_to a reservation (like it does belong to a Tourist
and HotelOwner
).
It's not only semantics that bother me. If I do hotel_owner.reservations.create(tourist: Tourist.last)
, it does create the Reservation, but the join model Conversation
is not created, leaving reservation.conversation
nil.
After a simple hotel_owner.reload
, hotel_owner.reservations
return nil.
What is the correct database design and Rails association model for something like this?
Upvotes: 1
Views: 225
Reputation: 3603
you can keep it simple like this:
class HotelOwner < ActiveRecord::Base
has_many :reservations
has_many :conversations
end
class Tourist < ActiveRecord::Base
has_many :reservations
end
# hotel_owner_id, tourist_id
class Reservation < ActiveRecord::Base
belongs_to :tourist
belongs_to :hotel_owner
has_one :conversation, dependent: :destroy
after_create { self.conversation.create! }
end
# reservation_id
class Conversation < ActiveRecord::Base
belongs_to :reservation
end
Then you can access these associations as follows:
# get all reservations and conversations for a hotel_owner
HotelOwner.last.reservations
HotelOwner.last.conversations
# all reservations made by a tourist
Tourist.last.reservations
# conversation associated to a reservation
Reservation.last.conversation
# get reservation, hotel_owner and tourist from a conversation
Conversation.last.reservation
Conversation.last.reservation.tourist
Conversation.last.reservation.hotel_owner
Upvotes: 0