bogardpd
bogardpd

Reputation: 287

Validating a Rails form field that has to run through another model before saving

I have a Ruby on Rails (Ruby 2.5.1, Rails 5.1) application which deals with flights; among other things, it has a Flight model and an Airline model, with Flight belongs_to :airline and Airline has_many :flights. Airline has an iata_code column which contains the two-letter IATA airline code (i.e. AA, UA, WN, F9).

For the flight entry form, I want the user to be able to enter an arbitrary IATA code, rather than selecting an airline from a dropdown. If the IATA code the user enters matches a record in the Airline table, then the Flight.airline_id column should be set to that airline's ID. If the IATA code doesn't match an airline in my database, then the airline should be created using the IATA code provided, and that new airline's ID should be assigned to Flight.airline_id.

I currently have this working by having an Airline IATA code field in my add/edit flight form (note that the form field is named :airline_iata instead of :airline_id):

<%= f.label :airline_iata, "Airline code" %>
<%= f.text_field :airline_iata, class: "form-control code airline-iata", maxlength: 2, placeholder: 'AA' %>

Then, in my Flights controller's create and update action, I try to look up the airline before I save the flight, and set the airline_id parameter (create is below, but update is similar):

def create
  params[:flight][:airline_id] = Airline.find_or_create_by!(:iata_code => params[:flight][:airline_iata].upcase).id
  @flight = current_traveler.flights.build(flight_params)

  if @flight.save
    redirect_to event_path(current_traveler.event)
  else
    render "new"
  end

end

This approach is working for me, except that I am unable to figure out how to do validation on the Airline IATA code field from the flight form. The Airline model validates the code (presence: true, length: { is: 2 }, uniqueness: { case_sensitive: false }), so if the user inputs an invalid IATA code, the Airline creation will fail, and thus the Flight creation will fail. However, I won't get the usual validation errors on the Flight form (e.g. error message, form fields with errors highlighted) since Flight's problem is that the airline_id (which isn't directly on the Flight form) isn't present, rather than that airline_iata in the Flight form is invalid.

How can I set it up so that this form field that has to interact with the Airport model before submission can run the Airport model's validation on a Flight form field, and return form validation errors accordingly?

Upvotes: 0

Views: 46

Answers (1)

Alexander Sysuiev
Alexander Sysuiev

Reputation: 721

You can try to use nested form attributes and add before_validation hook which will override the association:

<%= f.fields_for :airline do |airline_form| %>
  <%= airline_form.label :iata_code, "Airline code" %>
  <%= airline_form.text_field :iata_code %>
<% end %>

In flight.rb you should have

accept_nested_attributes_for :airline
before_validation :check_existing_airline, on: :create

def check_existing_airline
  if airline.new_record? && existing_airline = Airline.find_by(iata_code: airline.iata_code)
    self.airline = exisitng_airline
  end
end

Upvotes: 1

Related Questions