Search code examples
ruby-on-railsrubyvalidationmethodsmodels

Need help writing a method to calculate duration (time) for Model/Views?


My Rails 4 app has an appointments table, which has the following attributes/columns: "date" (datatype date), "starts_at" (datatype time), and "ends_at" (datatype time). I do not have a "duration" column, but want to calculate the difference between "ends-at" and "starts-at" times. I want the duration so I can put a validation in the Appointment model to prevent users from making appointments for less than 30 minutes or for more than 2 hours. I used Google to research the issue, but didn't find anything particularly helpful for what I'm trying to do. I'm learning Rails, and trying to pick up best practices. I'm assuming the place to put the "duration" logic is in the model, as opposed to the views. Right now, I don't believe users will need to see duration data. I started writing a "duration" method for the model, but can't get the logic to work properly. Here's what I've done so far:

protected

  def duration
    (self.ends_at - self.starts_at).to_i
  end

I call the method with:

validate :duration, numericality: {greater_than: 30, less_than: 120}

When I execute the code it doesn't generate any errors, but the validation does not appear to do anything. Users are still able to make appointments for less than 30 minutes or for more than 2 hours. I need to prevent both scenarios from being stored in the DB. I played around with some if and else statements in the method, but no success.

Questions:

  1. What's wrong with my current code?

  2. Is there a better way to approach the problem to accomplish my objective?

  3. If I do decide later to present "duration" information to users via the views, how do I accomplish that via embedded Ruby code?

I appreciate any help and advice!


Solution

  • Here is the approach I would probably take:

    The comments to the right were added just to explain a little of what was going on.

    validate :duration
    
    private
    
    def duration
      length = (ends_at - starts_at) / 60
      return if length.between?(30, 120) # bail out of validation if length falls between the two integers
    
     errors.add(:base, 'Your error message') # you can change this to something like `:ends_at` if it's preferable..
    end
    

    More information on comparables in Ruby

    If you want to present the duration later on, I would most likely go the route of using a presenter. But you could also just create an instance method on that model.

    # Public: get the duration of the appointment in minutes
    #
    def duration
      (ends_at - starts_at) / 60
    end
    

    if this is the case, then you can do a little refactoring to the valiation

    validate :correct_duration
    
    private
    
    def correct_duration
      length = self.duration
      return if length.between?(30, 120) # bail out of validation if length falls between the two integers
    
     errors.add(:base, 'Your error message') # you can change this to something like `:ends_at` if it's preferable..
    end
    

    Hope this works for ya.