Rails from first principles: Building a has_many bi-directional association
A great way to learn is by trying to explain, so as part of my rails association learning process, I’ll attempt to explain a slightly more complex bi-directional association between two models. This is part of the “Building advanced forms” exercise of The Odin Project.
Objective: I want to build a proof of concept flight-booking app, and the first requirement is to create associations between an Airport model and a Flight model.
Steps:
- Each airport will have many flights, and each flight will belong to an airport. Airports have 3-letter international codes, e.g. LIS for the Lisbon international airport.
- Each flight, however, will need both origin and destination airports.
- Each airport will have departing and arriving flights.
So what does this mean for our model associations?
Step 1:
The airport model has a Code variable, which is a string field, such as “SFO” or “LIS”. This association creates the Airport.find(1).flight and Flight.find(1).airport methods which let you query Airport flights and flight airports, respectively.
Step 2:
Now we need to separate flight airports into origin airports and destination airports, since flights fly from A to B. We will call these from_airport and to_airport to make the relationships easier to understand. The Flight associations are changed to:
belongs_to :from_airport, class_name: “Airport”
belongs_to :to_airport, class_name: “Airport”
This means that each flight will have a from_airport and a to_airport, but that these are simply different instances of the Airport model. This means we have to create a from_airport_id field and a to_airport_id field in the Flight model in order to associate each Flight to two airports, the origin and destination ones.
Step 3:
The next step is to add departures and arrivals to the airport model. A departing flight of an airport is a flight whose from_airport field corresponds to the airport id of that airport. An arriving flight is a flight whose to_airport field corresponds to the airport’s id.
So what we want to do is to call these flights “departing_flights” and “arriving_flights”, and associate them to a from_airport_id field and a to_airport_id field, in the Flight model. This association is done by setting a foreign_key.
has_many :departing_flights, foreign_key: :from_airport_id, class_name: “Flight”
has_many :arriving_flights, foreign_key: :to_airport_id, class_name: “Flight”
In narrative form, this means that Airports have flights, and they are either departing or arriving depending depending on whether the from_airport_id or to_airport_id field corresponds to the airport ID.
Likewise, Flights have “from airports” (origins) and “to airports” (destinations), which is set by the ids in from_airport_id and to_airport_id.
We can now use methods such as Airport.first.departing_flights to get the departing flights of the first airport, and Flight.first.from_airport to get the origin of the first flight.
If we wanted to, we could change from_airport to origin and to_airport to departure but we’ll leave it as it is in the interest of simplicity. We could also add inverse_of to make database querying more efficient, but we’ll also leave that out as it’s not a strict requirement.
And that’s it. Our app now has Airports with many departing and arriving flights, as well as Flights with origins and destinations.
— — —
Hope this was insightful. Feedback is always welcome.