Search code examples
ruby-on-railsvalidationmodel

Model validations for specific scenario, Rails


I've using a calendar gem that uses start_time for the date (YYYY-MM-DD).

For example purposes I've got Meeting, which has start_time string and postponed boolean attributes.

@meeting.start_time must be at least one day in the future (the day after today), but you can't set a new start_time (Meeting Date) if @meeting.postponed, only if @meeting.postponed == false

Also, if @meeting.postponed, cannot un postpone it without setting a new @meeting.start_time (that is at least one day in the future).

I would like validations so when updating the Meeting (if @meeting was previously postponed), I want to unselect the @meeting.postponed checkbox and submit a new @meeting.start_date in the form, and have it pass in the same submission.

if in the controller, something like:

@meeting.attributes = meeting_params

if @meeting.start_time != "" && @meeting.start_time != nil
  if @meeting.start_time_changed?
    if @meeting.posteponed
      @error = "Can't change the Meeting Date if the Meeting is still postponed."
    else
      if @meeting.start_time.to_date <= Date.today
        @error = "Meeting must be at least one day in the future."
      end
    end
  else
    if @meeting.postponed_changed? && @meeting.postponed == false
      @error = "You can't un postpone the Meeting without setting a new Meeting Date."
    end
  end
else
  @error = "Meeting Date cannot be blank."
end

How can I do this with validations?

UPDATE: As per max, this works for my specific scenario:

validates :start_time, presence: true
  validate :start_time_must_be_in_future, 
    if: :start_time? # prevents a nil error
    #unless: :postponed?,

  validate :start_time_cannot_change,
    if: :postponed?,
    on: :update    
  validate :postponed_cannot_become_false_unless_a_new_start_time_is_submitted_that_is_not_in_the_past,
    if: !:postponed? && :postponed_changed?,
    on: :update

private

  def start_time?
    !start_time.nil? && start_time != ""
  end

  def start_time_must_be_in_future
    errors.add(
      :start_time,
      "-- The date of the meeting cannot be in the past (must be either today or in the future)."
    ) if start_time.to_date < Date.today 
  end

  def start_time_cannot_change
    errors.add(
      :start_time,
      "-- Can't change the Meeing Date if the Event is still postponed."
    ) if start_time_changed? && start_time_must_be_in_future
  end

  def postponed_cannot_become_false_unless_a_new_start_time_is_submitted_that_is_not_in_the_past
    errors.add(
      :postponed,
      "-- Can't un postpone a Meeting without setting a new Meeting Date that is either today or in the future. Please submit a new Meeting Date if you are updating the Meeting as no longer postponed."
      ) if postponed_changed? && !postponed? && !start_time_changed?
  end

Solution

  • Instead of lumping all the buisness logic of your model into a single crazy tree of of conditional branches use individual validations for each aspect. I think you're actually overcomplicating it.

    You can control when validations are fired with the on: option and writing custom validators is as easy as creating a method and using validate :method_name. If you want to create more complex validators create a validator class.

    class Meeting
      validates :start_time, presence: true
      validate :start_time_must_be_in_future, 
        if: :start_time?, # prevents a nil error
        unless: :postponed?,
        on: :create       
      validate :start_time_cannot_change,
        if: :postponed?
        on: :update
    
      private
    
      def start_time?
        !start_time.nil?
      end
    
      def start_time_must_be_in_future
        errors.add(
          :start_time,
          "Meeting must be at least one day in the future."
        ) if start_time.to_date <= Date.today 
      end
    
      def start_time_cannot_change
        errors.add(
          :start_time,
          "Can't change the Meeting Date if the Meeting is still postponed."
        ) if will_save_change_to_start_date?
      end
    end
    

    There are gems such as validates_timeliness that make temporal validations less onerous.