When creating a migration for a model like Contract
below, we have to add a foreign key to both the supplier_id
and the customer_id
on the contracts
table. Both of these foreign keys should point to the organisations
table - defined by the Organisation
model.
erDiagram
Contract {
int supplier_id FK "organisation.id"
int customer_id FK "organisation.id"
}
Organisation ||--o{ Contract : "as supplier"
Organisation ||--o{ Contract : "as customer"
Rails can handle standard migrations for references without modification e.g.
#...
t.references :organisation, null: false, foreign_key: true
#...
In our scenario, a little modification is required and documentation for this is thin - [references].
For a start we not using inferable reference names i.e. we donβt have a suppliers
or customers
table but instead we want to use the organisations
table.
Migrations
Here we will have to help Rails locate the relevant table - in this case, the organisations
table.
The documentation does not give us any clues on how to define the table that the foreign key references, however, tucked away in the codebase we found the to_table
option on the foreign key.
With this option, we can define
#...
t.references :supplier, null: false, foreign_key: {to_table: :organisations}
t.references :customer, null: false, foreign_key: {to_table: :organisations}
#...
Modelling
On the model side of things, it gets a little confusing too.
To start with the Contract
model needs relationships that specify the class_name
.
class Contract < ApplicationRecord
belongs_to :supplier, class_name: "Organisation"
belongs_to :customer, class_name: "Organisation"
end
On the other side, we have the Organisation
model.
The Organisation
model has two main relationships has_many :customers
and has_many :suppliers
, both of which should return a collection of Organisation
records.
To have both of these relationships will require a through
relationship and this is where naming can become difficult1.
The table below describes the relationships on the Organisation
model.
name | description |
---|---|
contracts_as_customer | Collection of Contract records where the Organisation is the customer |
contracts_as_supplier | Collection of Contract records where the Organisation is the supplier |
customers | Collection of Organisation records that are Customers of the Organisation |
suppliers | Collection of Organisations records that are Suppliers to the Organisation |
The final Organisation
model may look like this
class Organisation < ApplicationRecord
has_many :contracts_as_customer, class_name: "Contract", foreign_key: :customer_id
has_many :contracts_as_supplier, class_name: "Contract", foreign_key: :supplier_id
has_many :customers, through: :contracts_as_supplier, source: :customer
has_many :suppliers, through: :contracts_as_customer, source: :supplier
end
-
The first iteration of this post revolved around People and Follows in a social network type scenario. Thankfully Organisations and Contracts are easier to follow.Β ↩