Recipe: Detecting Required Fields in Rails

It's a familiar problem. You're creating a form and you want to mark certain fields as required. You'd rather not hard-code which fields are required, because this might change over time. Plus, you've already got validations in your model that check for the presence of required fields, and you'd really rather not duplicate any of that logic. So, how can you tell whether a field associated with an ActiveRecord model is required?

The Problem

Let's assume that we have a model called Author, with required fields of first_name and last_name. The model looks like this:

   class Author < ActiveRecord::Base

      validates_presence_of :first_name
      validates_presence_of :last_name

   end

The validations ensure that the first_name and last_name attributes of the model will have values. The bottom line is that the model has information about which fields are required. We need to be able to interrogate the model to determine if a field is required.

The Solution

After a little bit of research, it turns out that ActiveRecord has a validators_on method. Let's fire up the Rails console and see how it works:

      console>  Author.validators_on(:first_name)
      => [#<ActiveModel::Validations::PresenceValidator:0x45c4770 
      @attributes=[:first_name, :last_name], @options={}>]

OK, that gives us an array of validations for the specified field, where each validation is an instance of some type of validation class. That's somewhat useful, but we can do better.

      console> Author.validators_on(:first_name).map(&:class)
      => [ActiveModel::Validations::PresenceValidator]

That's even better. Now we have an array of classes, where each element is the class of a validator that has been placed on the specified attribute. The "&:class" argument to the map method causes the class method to be executed for each element in the array.

Now that we know how to get a list of the validations that are on a particular field, we can write a method that will return TRUE or FALSE based on whether a field is required. I'm going to throw in one wrinkle, though. I'd like to be able to call the method using either a class or an instance of a class.

   def required?(obj, attr)
      target = (obj.class == Class) ? obj : obj.class
      target.validators_on(attr).map(&:class).include?(
         ActiveModel::Validations::PresenceValidator)
   end

I can put this method in my application_helper.rb file for my Rails application. Then I can call it from a view in two ways:

      required?(Author, :first_name)

Or, assuming that the controller has passed an instance of the Author model to the view as @author:

      required?(@author, :first_name)

By the way, this call works just fine, as well:

      required?(@author, "first_name")

Now my view logic can easily check whether a field is required.


   <%= form_for(@author) do |f| %>
      <p><%= f.text_field :first_name %>
      <% if required?(@author, :first_name) %>*<% end %></p>
 


Comments

David Keener By Stefan Huska on Thursday, August 11, 2011 at 10:25 AM EST

Thanks! Nice solution.


David Keener By zuev.evgenii on Thursday, April 18, 2013 at 06:39 PM EST

Typo:

   target.validators_on(attribute).map(&:class).include?(
      ActiveModel::Validations::PresenceValidator)

should be:

   target.validators_on(attr).map(&:class).include?(
      ActiveModel::Validations::PresenceValidator)




David Keener By dkeener on Friday, April 19, 2013 at 05:03 AM EST

Argh. You're right. Thanks for the correction, Zuev. I'll make that change to the original blog entry shortly.

Jeez, this article has been read like 2000+ times since it was first posted, and you're the first person to point out that little flaw. Sigh.




David Keener By dkeener on Friday, April 19, 2013 at 05:06 AM EST

Corrected.


David Keener By Steven Nunez on Friday, July 12, 2013 at 01:50 AM EST

Great post! If you're using Rails 4 it's:

target.validators_on(attr).map(&:class).include?(ActiveRecord::Validations::PresenceValidator)




David Keener By dkeener on Monday, August 26, 2013 at 02:52 PM EST

Thanks for the great update, Steven. I like to make sure these articles stay relevant.


Leave a Comment

Comments are moderated and will not appear on the site until reviewed.

(not displayed)