Search code examples
ruby-on-railsdatevalidationproc

Rails conditional validation of year, month and day with leap years (without date object)


I am using a model with separate values for start_year, start_month, start_day and end_year, end_month and end_day. I have good reason to not use the built in Ruby Date object in this case. Only the start_year value should validate for presence.

What I want to do: check if the start_day or end_day values are valid for a given month, including leap years. So if a user enters January for the month, it should be 1..31, but if it's February, it should be 1..28 unless the year is a leap year (i know I can just feed the year into a Date object and check for leap so that's no problem).

I'm just struggling with the syntax, I think the problem is that i fundamentally misunderstand how to use conditional validation but can't figure out how, this is part of my code:

  validates :start_day, inclusion: { in: 1..31, allow_nil: true, message: "invalid: up to 31 is allowed for the selected month" }, if: proc { |a| a.start_month.odd? }
  validates :start_day, inclusion: { in: 1..30, allow_nil: true, message: "invalid: up to 30 is allowed for the selected month" }, if: proc { |a| a.start_month.even? } && proc { |a| a.start_month != 2 }
  validates :start_day, inclusion: { in: 1..28, allow_nil: true, message: "invalid: up to 28 is allowed for the selected month and year" }, if: proc { |a| a.start_month == 2 } && proc { |a| !Date.leap?(a.start_year) }
  validates :start_day, inclusion: { in: 1..29, allow_nil: true, message: "invalid: up to 29 is allowed for the selected month and year" }, if: proc { |a| a.start_month == 2 } && proc { |a| Date.leap?(a.start_year) }

When I try to create a new object with valid dates, it works. When I try to create one with an invalid date, such as January 32 of the year 100, it throws error messages for the top 3 of these validations, but the if statements are supposed to make sure only the relevant validation (for odd or even months, or february) executes. If I leave out the month, it throws an error because it tries to pass a nonexistent month to call the .odd? or .even? method on, same problem with Date.leap? and the year.

What I want: the model should ignore certain validations if other values are not present, and only apply those validations that are appropriate given the other values. I'm pretty sure I'm using either these if statements, or the procs, in the wrong way somehow but can't figure out why. Any help is appreciated.


Solution

  • That is a case where you can use custom validator or custom methods validation

    And do something like that :

    validate :days_in_a_month
    
    private
    
    def days_in_a_month
      err, days = false, 0
      case
      when start_month.odd?
        err, days = true, 31 if not start_day.in? 1..31
      when start_month.even? && start_month != 2
        err, days = true, 30 if not start_day.in? 1..30
      when start_month == 2
        if Date.leap?(start_year)
          err, days = true, 29 if not start_day.in? 1..29
        else
          err, days = true, 28 if not start_day.in? 1..28
        end
      end
      if err
        errors.add(:start_day, "invalid: up to #{days} is allowed for the selected month")
      end
    end