Rails | Use Nested Attribute as Foreign Type For Polymorphic Association?

55 views Asked by At

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!

1

There are 1 answers

3
Thanh On BEST ANSWER

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