ruby-on-railsrubyruby-on-rails-5wicked-gem

Making field validate only if user specified a certain value, otherwise optional


There is a form that a user submits which then gets validated by the model. I only want the field "Province / State" to validate if the "Country" is either "CA" (Canada) or "US" (USA)

The form is set up a little differently because we are doing a multiple step from process.

Here is the controller.

    def update
        case step
        when :step1
          wizard_params = profile_params()
          wizard_params[:wizard] = 'step1'

          @profile = current_user.profile
          @profile.update(wizard_params)

          render_wizard @profile
end

    private
        def profile_params
          # There are more params although I stripped them for the simplicity of this example
          params.require(:profile).permit(:state_id, :country)
        end

Profile.rb

  belongs_to :state, :class_name => "ProvinceState", :foreign_key => :state_id, optional: true

I hard coded optional: true but I only want optional:true if the user selected CA/US OR the field saved is CA/US.

I took a look at lambda and it could be something I need.

For Example:

belongs_to :state, :class_name => "ProvinceState", :foreign_key => :state_id, optional: lambda | obj | self.country == CA || self.country == US ? true : false 

Solution

  • Unfortunately, you cannot (currently) provide a lambda to optional - see the source code:

    required = !reflection.options[:optional]
    

    If required, Rails just adds a presence validation like this:

    model.validates_presence_of reflection.name, message: :required
    

    Therefore as a workaround, you could do this in two parts: First specify the association as optional; then explicitly make it required for your condition:

    belongs_to :state, :class_name => "ProvinceState", :foreign_key => :state_id, optional: true
    validates :state_id, presence: true, if: ->{ %w[CA US].include?(country) }
    

    If the logic gets significantly more complicated, you may wish to move this into a separate method/class instead of an inline lambda. See: Performing Custom Validations