ruby-on-railsrubyvalidationactivemodelchronic

Rails: Custom validations using ActiveModel


I am currently trying to make custom validations work with an input of dates, but, unfortunately, it doesn't seem to work.

There are two pages inside the application, Index page and Search page. Inside the index page there is a text field that takes in a date. I am using Chronic gem which parses text into dates. If the date is invalid, Chronic returns nil. If it is valid, it redirects to search page and shows the date.

The code I wrote so far doesn't seem to work properly, but what I want to achieve is..

1) to validate that Chronic doesn't return nil

2) to validate that date is greater than today's date

Please note that I am not using a database with this, I just want to be able to validate inputted date without ActiveRecord. If someone could help me with this, your help will be greatly appreciated.

views/main/index.html.erb

<%= form_tag({controller: "main", action: "search"}, method: "get") do %>
    <%= label_tag(:q, "Enter Date:") %>
    <%= text_field_tag(:q) %>
    <%= submit_tag "Explore", name: nil %>
<% end %>

views/main/search.html.erb

<%= @show_date %>

main_controller.rb

def search

    passed_info = Main.new

    if passed_info.valid_date?
        @show_date = passed_info
    else
        flash[:error] = "Please enter correct date!"
        render :index => 'new'
    end

end

models/main.rb

class Main

  include ActiveModel::Validations
  include ActiveModel::Conversion
  extend  ActiveModel::Naming

  attr_accessor :q

  validates_presence_of :q

  def initialize(params={})
    params.each do |attr, value|
    self.public_send("#{attr}=", value)
    end if params
  end

  def persisted?
    false
  end

  def valid_date?
    require 'chronic'
    if Chronic.parse(q).nil? || Chronic.parse(q) < Time.today
        errors.add(:q, "is missing or invalid")
    end
  end

end

EDIT:

this is what goes wrong...

localhost:3000

enter image description here

then it redirects to ..

localhost:3000/main/search?utf8=%E2%9C%93&q=invalid+date+test

enter image description here

No validation, no date, nothing..


Solution

  • Just as correctly suggested by ABMagil, I would like to post the full solution to my answer. In fact, this answer can apply really to anyone who wants to use validations using ActiveModel, with or without Chronic gem or dates involved. It can act as a valid template so to speak.

    Frankly, most of my mistakes came from a really poor, at the time, understanding of what I actually tried to achieve. Most of the code needed major refactoring, see below the updates that I had to make. I tried to keep the code as well documented as possible.

    Solution:

    views/main/index.html.erb

    <%= form_for @search, url: { action: "search" }, 
                          html: { method: :get } do |f| %>
    
      # Displays error messages if there are any.
      <% if @search.errors.any? %>
        The form contains <%= pluralize(@search.errors.count, "error") %>.<br />
        <% @search.errors.each do |attr, msg| %>
          <%= msg %><br />
        <% end %>
      <% end %>
    
      <%= f.label :q, "Enter Date:" %>
      <%= f.text_field :q %>
      <%= f.submit "Explore", :class => 'submit' %>
    <% end %>
    

    views/main/search.html.erb - same as before

    <%= @show_date %>

    main_controller.rb

    def index
      # Initializes a string from the form to a variable.
      @search = Search.new
    end
    
    def search
      # Retrieves the input from the form.
      @search = Search.new(params[:search])
    
      # Checks for validity, 
      # If valid, converts a valid string into a date.
      # Redirects to search.html.erb 
      # If not valid, renders a new index.html.erb template.
      if @search.valid?
        @show_date = (Chronic.parse(params[:search][:q])).to_date   
      else
        render :action => 'index'   
      end
    end
    

    models/main.rb

    class Main
      include ActiveModel::Validations
      include ActiveModel::Conversion
      extend  ActiveModel::Naming
    
      # Accepts the passed attribute from the form.
      attr_accessor :q
    
      # If form is submitted blank, then output the prompting message.
      validates_presence_of :q, :message => "This text field can't be blank!"
      # Two custom validations that check if passed string converts to a valid date.
      validate :date_is_valid
      validate :date_not_before_today
    
      # Initializes the attributes from the form.
      def initialize(attributes = {})
        attributes.each do |name, value|
          send("#{name}=", value)
        end
      end
    
      # Checks for persistence, i.e. if it's a new record and it wasn't destroyed. 
      # Otherwise returns false.
      def persisted?
        false
      end
    
      # ABMagil's code used for custom date validations
      private
        require 'chronic'
        def date_is_valid
          if Chronic.parse(q).nil?
            errors.add(:base, "Date is invalid")
          end
        end
        def date_not_before_today
          if !Chronic.parse(q).nil?
            if Chronic.parse(q) < Date.today
              errors.add(:base, "Date cannot be before today")
            end
          end
        end
    end
    

    Result:

    Result