Search code examples
ruby-on-railsrails-activerecord

Why is this Rails collection_select not rendering the association attribute?


Flight - belongs_to :departure_airport, Airport has_many :departing_flights.

I am trying to create a drop down select form element within a Rails form_with where the association's :city value for each option is an attribute on an association:

View form code - flight#index:

<%= form_with(url: flights_path, method: :get) do |form| %>
  <div>
    <%= form.label :departure_airport, 'Departure Airport' %>
    <%= form.collection_select :departure_airport, @flights, :departure_airport_id, :departure_airport.city, prompt: 'Select Departure Airport' %>    
  </div>
  <div>
    <%= form.submit 'Search Flights' %>
  </div>
<% end %>

Flight controller code:

class FlightsController < ApplicationController
  def index
    @flights = Flight.all
  end
end

Flight.rb:

class Flight < ApplicationRecord
  belongs_to :departure_airport, class_name: 'Airport'
  validates_presence_of :departure_airport
end

Airport.rb:

class Airport < ApplicationRecord
  has_many :departing_flights, class_name: 'Flight', foreign_key: 'departure_airport_id'
end

I can see that the association is working because the html displays the actual airport object reference string as the name/label for each dropdown option. But for the life of me I cannot get the each option to be named with an attribute on the association:

Html rendered form extract:

<form action="/flights" accept-charset="UTF-8" method="get">
  <div>
    <label for="departure_airport">Departure Airport</label>
    <select name="departure_airport" id="departure_airport"><option value="">Select Departure Airport</option>
<option value="8">#&lt;Airport:0x00000001126f0730&gt;</option>
<option value="60">#&lt;Airport:0x0000000112728630&gt;</option>
<option value="47">#&lt;Airport:0x0000000112713140&gt;</option>
<option value="33">#&lt;Airport:0x00000001126fbdd8&gt;</option>
etc ...
<option value="42">#&lt;Airport:0x0000000112c9b568&gt;</option></select>    
  </div>
  <div>
    <input type="submit" name="commit" value="Search Flights" data-disable-with="Search Flights">
  </div>
</form>

I've tried any of the various attrs/columns of an airport object (e.g. :departure_airport.name, :departure_airport.country) and none will display as the label for an individual option.

Can anyone see what I'm doing wrong here?

The associations are definitely there are model level, as illustrated by this rails console object & association querying:

3.1.2 :012 > f = Flight.first
  Flight Load (1.4ms)  SELECT "flights".* FROM "flights" ORDER BY "flights"."id" ASC LIMIT $1  [["LIMIT", 1]]
 => 
#<Flight:0x000000010280c9a8
... 
3.1.2 :013 > f.departure_airport_id
 => 8 
3.1.2 :014 > f.departure_airport.name
  Airport Load (1.0ms)  SELECT "airports".* FROM "airports" WHERE "airports"."id" = $1 LIMIT $2  [["id", 8], ["LIMIT", 1]]
 => "Bordeaux Airport" 
3.1.2 :015 > f.departure_airport.city
 => "Bordeaux" 
3.1.2 :016 > f.departure_airport.country
 => "France" 

3.1.2 :017 >


Solution

  • :departure_airport.city this calls city method on a Symbol :departure_airport, not the association.

    You have to use attributes and methods that are in Flight model:

    <%= form.collection_select :departure_airport_id, @flights,
      :departure_airport_id,
      :departure_airport_city
    %>
    
    class Flight < ApplicationRecord
      belongs_to :departure_airport, class_name: 'Airport'
      # this validation is redundant, belongs_to association is required by default
      # validates_presence_of :departure_airport
    
      # this is the same as
      #
      #   def departure_airport_city
      #     departure_airport.city
      #   end
      #
      delegate :city, to: :departure_airport, prefix: true
    end
    

    https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-collection_select