Reputation: 63
I have a model that has a has_and_belongs_to_many
relationship with two other models. For example, I have a Message
model which has recipients. However, the recipients can be one of two different types of models. For example, InternalContact
, and ExternalContact
.
It is easy to set up relations where I can get either the Message's external contacts (Message.first.external_contacts
) or its internal contacts (Message.first.internal_contacts
). And then I can, in the same vein, easily add to this relationship array (Message.first.external_contacts << ExternalContact.first
)
What I want is the ability to simply things and make this more efficiently by calling something like:
Message.first.recipients # an array of both external and internal contacts
Message.first.recipients << ExternalContact/InternalContact # (add either model)
Any direction is appreciated, I'm definitely stuck!
Upvotes: 0
Views: 814
Reputation: 18454
With explicit has-and-belongs-to-many (aka has_many through:
) relation model you'll have more control over the relation and can make one end of it polymorphic:
class Message < ApplicationRecord
has_many :message_recipients
has_many :internal_recipients, through: :message_recipients, source: :recipient, source_type: "InternalContact"
end
class InternalContact < ApplicationRecord
has_many :message_recipients, as: :recipient
has_many :messages, through: :message_recipients, inverse_of: :internal_recipients
end
class MessageRecipient < ApplicationRecord
belongs_to :message
belongs_to :recipient, polymorphic: true
end
msg = Message.create!
rec = InternalContact.create!
msg.internal_recipients << rec
msg.message_recipients.each{|mr|
mr.recipient # here can be any model
}
Upvotes: 1
Reputation: 101831
One way to do this if you want to avoid a polymorphic association is to use Single Table Inheritance.
One major reason you would want to do this is to be able use a foreign key to ensure referential integrity which is not possible with polymorphism.
Since the types share a table you can also treat them like a homogeneous collection when it comes to ordering.
But single table inheritance is only really suited if the different types are very similar in nature and are interchangeable.
First create a contacts
table where you will store all the different types.
class CreateContacts < ActiveRecord::Migration[5.2]
def change
create_table :contacts do |t|
t.string :type
# and whatever columns you need
t.string :email
# ...
t.timestamps
end
end
end
Pay attention to the type
column. ActiveRecord will use this to store the class name of the subclasses. Then setup InternalContact and ExternalContact to inherit from the base Contact
class:
class InternalContact < Contact
# ...
end
class ExternalContact < Contact
# ...
end
This lets you implement whatever logic you want (such as validations and callbacks) for each subtype.
Start with the join table:
class CreateMessageRecipients < ActiveRecord::Migration[5.2]
def change
create_table :message_recipients do |t|
t.belongs_to :message
t.belongs_to :recipient, foreign_key: { to_table: :contacts }
t.timestamps
end
end
end
Lets setup the associations with has_many through:
and a join model instead of has_and_belongs_to_many
which is very limited:
class Contact < ApplicationRecord
has_many :message_recipients, foreign_key: :recipient_id
has_many :messages, through: :message_recipients
end
class MessageRecipient < ApplicationRecord
belongs_to :message
belongs_to :recipient, class_name: 'Contact'
end
class Message < ApplicationRecord
has_many :message_recipients
has_many :recipients, through: :message_recipients
end
Upvotes: 1
Reputation: 375
You can have a separate recipient type table and then map messages only with recipients
Recipient Recipient_type Message Message_recipient_map
---------- --------------- ------- -------------------
id id id message_id
recipient_type_id recipient_id
Then you can have has_many_through relationship between message and recipient and can access all recipients of the message by calling message.recipients
Hope this helps you!
Upvotes: 1