Search code examples
ruby-on-railsactiverecordruby-on-rails-6has-one

has_one association with optional: true?


In a simple booking app:

  • When user selects a seat, a TempAppointment is created.
  • After the seat has been paid for, an Appointment is created based off the info in the TempAppointment record.

The Appointment record cannot be created first, since the passenger may not pay, in which case the TempAppointment stays as-in, and an associated Appointment record never gets created.

My natural thinking is that a TempAppointment has_one Appointment (which works), but when I add optional: true, I see errors:

class TempAppointment < ApplicationRecord
  has_one :appointment, optional: true 
end

try to create a new TempAppointment

ta = TempAppointment.new(cruise_id: 1, passenger_id: 1, start_time: start_time, end_time: start_time + 3600)
ArgumentError: Unknown key: :optional. Valid keys are: :class_name, :anonymous_class, :foreign_key,
 :validate, :autosave, :foreign_type, :dependent, :primary_key, :inverse_of, :required, :as, :touch

Why can't has_one work with optional: true?


Solution

  • has_one is optional: true by default (even if it isn't really an option of this method, I mean has_one never means it is require)

    So if you set the optional: true to the other model, be careful, this means that this other model doesn't need to be in a relationship with the first one. It's a one way dependence.

    # your model_a doesn't need any model_b to exist
    class ModelA < ApplicationRecord
      belongs_to :model_b, optional: true
      
      [...]
    end
    
    # same for model_b, it can exist without model_a
    class ModelB < ApplicationRecord
      has_one :model_a
      
      [...]
    end
    

    But if you do

    # your model_a has to belong to a model_b, otherwise it will fail
    class ModelA < ApplicationRecord
      belongs_to :model_b
      
      [...]
    end
    
    # your model_b can still exist without model_a
    class ModelB < ApplicationRecord
      has_one :model_a
      
      [...]
    end