In Rails, I would like to structure my models such that the main record (lets call it Shipment
) has a detail record (base type: ShipmentDetail
) that is polymorphic such that the structure of the data can change based upon a nested attribute in another association (let's say: client.name
).
The ShipmentDetail
class contains a payload
jsonb type field with client specific information. It can be sub-classed so that we can pull out that client-specific information easily, such as ClientA::ShipmentDetail
, ClientB::ShipmentDetail
, etc. In this way, all ShipmentDetail instances are stored in the same shipment_details
table in the database.
Where I am having trouble is figuring out how to properly configure the association between Shipment and ShipmentDetail.
Here is the base ShipmentDetail class:
class ShipmentDetail < ApplicationRecord
self.abstract_class = true
self.table_name = "shipment_details"
has_one :shipment
end
And here is how I have currently attempted to model the Shipment class:
class Shipment < ApplicationRecord
belongs_to :client, optional: true
belongs_to :details, class_name: :shipment_details, \
polymorphic: true, foreign_key: :id, foreign_type: :client_name, optional: true
end
Unfortunately, if I attempt to assign something to the details
association I get an error like:
ActiveModel::MissingAttributeError: can't write unknown attribute `client_name`
Please advise on how I can properly model this association - is there a trick to configuring the foreign_type
to use a nested attribute? Thanks!
Your associations don't match with your description.
the main record (lets call it Shipment) has a detail record (base type: ShipmentDetail) that is polymorphic such that the structure of the data can change based upon a nested attribute in another association (let's say: client.name).
so Shipment has a ShipmentDetail, the associations should be:
class Shipment < ApplicationRecord
has_one :shipment_detail
end
class ShipmentDetail < ApplicationRecord
belongs_to :shipment
belongs_to :client
end
Client-speficic shipment details can be subclass, and client information can be pull-out:
class ClientAShipmentDetail < ShipmentDetail
def client_data
if payload['client'] == 'clientA'
# return Client A data
end
end
end
class ClientBShipmentDetail < ShipmentDetail
def client_data
if payload['client'] == 'clientB'
# return Client B data
end
end
end
In this design, you can get a dynamic instance of Shipment Detail specific class by using (Single Table Inheritance)
Table shipment_details
just need to add a string column name type
so that when shipment_detail
association is loaded, it will return correct object of class type
.
Example data:
shipments table
id|name|
1|shipment1
2|shipment2
shipment_details table
id|shipment_id|type|
1|1|ClientAShipmentDetail
2|2|ClientBShipmentDetail
shipmment1 = Shipment.find(1)
shipment1.shipment_detail # Return instance of ClientAShipmentDetail class
shipmment2 = Shipment.find(2)
shipment2.shipment_detail # Return instance of ClientBShipmentDetail class