Is this your first visit? You may want to subscribe to the feed.

Validations on empty (not nil) attributes

One of the first problems that I ran into when I started using Rails was trying to validate the format of attributes that aren’t required. Most of the validations have an :allow_nil option, but the problem is that when a form is submitted with empty form fields, those fields are empty strings instead of nil values. So the validation fails because the attribute is not nil.

For example, here’s a Person model with a validation on :social_security_number, an optional attribute:

class Person < ActiveRecord::Base
  validates_format_of :social_security_number,
    :with => /\d{3}[-]?\d{2}[-]?\d{4}/, :allow_nil => true
end

When this model is used in a form, validation will fail if the social security number field is left blank, even though :allow_nil is set to true.

The solution

It turns out that the solution is really simple: a before_validation callback that just goes through and sets all the empty attributes to nil.

class ActiveRecord::Base
  before_validation :clear_empty_attrs
protected
  def clear_empty_attrs
    @attributes.each do |key,value|
      self[key] = nil if value.blank?
    end
  end
end

I’ve packaged this little nugget into a plugin. Install it and go on your merry validating way.

script/plugin install -x git://github.com/collectiveidea/clear_empty_attributes.git
Code: plugin, rails, validations Feb 06, 2007 ● updated Jun 19, 2008 16 comments

16 comments

  1. The other thing you could have done was:

    
    class Person < ActiveRecord::Base
      validates_format_of :social_security_number,
        :with => /\d{3}[-]?\d{2}[-]?\d{4}/, :if => :social_security_number?
    end
    
    
    K. Adam Christensen K. Adam Christensen February 07, 2007 at 07:55 AM
  2. Excuse my rudeness with the first comment.

    Your plugin does make sense because there is the parameter :allow_nil to use. This certainly allows for that usage in the context of what you have.

    That aside, I don’t know if that is important to warrant the use of this plugin. The example I gave performs the check in the context of what you were after.

    What if your first form does not contain your social security number? Then certainly the :allow_nil option makes sense because there will not be an empty form field to pollute. Now say you have another form, which does ask for the social security number. Now your validation will fire off. The :allow_nil can almost be seen as “validate this when I ask for it”.

    K. Adam Christensen K. Adam Christensen February 07, 2007 at 08:35 AM
  3. You’re right, the plugin is not entirely neccesary. However, I think it is a better solution.

    ActiveRecord performs very transparent type-casting. Dates that come in as strings get automatically cast to Date; numbers get cast to Numeric. In my opinion, empty strings should get cast to nil.

    I can’t think of a use-case where I would want to store and use and empty string value.

    Brandon Brandon February 07, 2007 at 09:54 AM
  4. II’m not 100% sure, but I believe you may be able to fix this in your schema. I think if you set the default value of the DB field to null, then rails will typecast empty entries to nil as expected.

    Not 100% sure though, worth a try.

    Justin Justin February 07, 2007 at 10:42 PM
  5. It does make sense in a round-a-bout kind of way – if you don’t specify the default value to be null, then both the database and rails are expecting a string, even if it is an empty one.

    Whether Rails picks up on this or not is another questions, I’m not currently somewhere I can test this out. I had a similar problem with strings and nil previously, and it’s what somebody mentioned in #rubyonrails.

    Justin Justin February 07, 2007 at 10:49 PM
  6. Just came across your posting and actually i think the problem is that you’re trying to use the allow_nil option on a method that it’s not designed to go with.

    Check the API and you’ll see that there’s not an allow_nil option for the validates_format_of method.

    In fact the only reason it doesn’t throw an error is because internally validates_format_of also calls the validates_each method which DOES have the allow_nil option but that’s still an incorrect use of the method.

    I solved the same problem a while back by doing it like this.

    I added a validates format on multiple optional SSN fields on a page like this: validates_format_of :byr1ssn1,:byr1ssn2,:slr1ssn1, :slr1ssn2, :with => /(?(\d{2})[\s.]?(\d{4})$)|$/, :message => ‘Not Recognized as a Valid SSN

    This allows the end user to input anything that looks like a SSN and I accept it (i.e. they can format it any way that’s convenient for them – spaces, dashes, etc). If they leave this blank – no errors are thrown.

    Then since I want all SSN’s in my database to be in a specific format – i added a before save method

    before_save :convert_ssns

    which just does this

    def convert_ssns
      regex  = /^(\d{3})[\s.]?(\d{2})[\s.]?(\d{4})$/
      ssns = :byr1ssn1,:byr1ssn2,:slr1ssn1, :slr1ssn2
      for ssn in ssns
        r =  regex.match(self[ssn])
        if r
          self[ssn] = "#{r[1]}#{r[2]}#{r[3]}" 
        end  
      end
    end

    So I get the best of all worlds – an optional SSN field, easier input for end-users, and a consistent standard for the data in the database. And yeah looking at that convert_ssns method now – I can see that I should probably go back and refactor it a little as it’s a bit verbose.

    Eldon Eldon February 13, 2007 at 01:57 AM
  7. Very nice work. I solved the very same problem with validates_uniqueness_of …, :allow_nil => true with this. But I at once ran in to another problem: I am using this validation on a set of single inheritance tables. One can no longer use the “master” class with your plugin because the type column is set nil instead of empty. Your plugin should be enhanced to not touch the type column.

    Kai Kai August 11, 2007 at 05:29 AM
  8. Kai,

    I’m not sure that I follow you. I’m using the plugin in several projects that use STI.

    Brandon Brandon August 17, 2007 at 11:14 AM
  9. Brandon,

    My type column was defined “NOT NULL” but was set to null if I stored the base class in the table. It look’s like Rails does not use the name of the base class for the type columns but instead an empty string which was set to nil by your plugin. Would it be possible to extend the plugin to only touch fields that Rails validates_uniqueness_of? I think the real power in this plugin is on validating uniqueness and not what your original intend was (validates_format_of).

    Kai Krakow Kai Krakow August 19, 2007 at 06:16 AM
  10. Kai,

    It look’s like Rails does not use the name of the base class for the type columns but instead an empty string which was set to nil by your plugin.

    What is your intention of defining the type column as “NOT NULL” (forcing that column to have a value) if, without this plugin, there is absolutely no way to create a record through Rails with the type column being set to NULL? Seems like you could just remove that constraint.

    I think the real power in this plugin is on validating uniqueness and not what your original intend was (validates_format_of).

    The intent of this plugin is that I don’t want empty values spread all over my database. In 99% of cases, I don’t care about empty values and want them treated as null. In the other 1%, I don’t use this plugin and copy the code out of it to customize it. It’s only a couple lines

    Brandon Brandon August 19, 2007 at 10:51 AM
  11. Brandon,

    [quote] What is your intention of defining the type column as “NOT NULL” (forcing that column to have a value) if, without this plugin, there is absolutely no way to create a record through Rails with the type column being set to NULL? Seems like you could just remove that constraint. [/quote]

    Well Rails allows to create records with this constraint active by the way it just sets the column to empty string. Of course this is suppressed by your plugin. But you are right: This constraint wasn’t optimal, it was just the default of the software I used to create this column. ;-)

    Kai Krakow Kai Krakow August 23, 2007 at 09:23 AM
  12. How about prepending ”^$|” to the regexp?

    Marcello Golfieri Marcello Golfieri February 01, 2008 at 11:56 AM
  13. Thanks! Solved my problem with nil != ”” for strings as mentioned here: http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/145fcc2701653186/8d5b8acdf688e681#8d5b8acdf688e681

    :)

    Joerg Battermann Joerg Battermann February 21, 2008 at 11:23 AM
  14. Yeah, actually my solution does work flawlessy in each scenario without any of the eventual drawbacks cited here. Nevertheless, having a dedicated :allow_nil would definitely be welcome. In fact, even if adding 3 characters to the regexp looks even faster than typing ”:allow_nil => true”, it does create some confusion on newcomers. Adding the option :allow_nil here too would standardize its use; people obviously expects that option for validates_format_of too (I was one of those, darn it!) Last but not least, “Convention” is cooler than “Configuration”, isn’t it?... (I just wonder where I got this from… ;-) Should be added to next Rails’ wish list.

    Marcello Golfieri Marcello Golfieri March 03, 2008 at 11:53 AM
  15. well, there’s built in :allow_blank => true to use instead or alongside with allow_nil

    andres andres May 21, 2008 at 11:25 AM
  16. Since I definitely did need to allow for a few not-null columns, but I wanted all the rest of my columns (in a large, dynamic schema) to always be nil if empty, I modified the plugin slightly to check the column definition to make sure it allows null, before converting to nil:

    def clear_empty_attrs
      # set any empty-string attributes to nil if the column allows null
      @attributes.each do |key,value|
        self[key] = nil if value.blank? && self.class.columns_hash[key].null
      end
    end
    neil neil June 17, 2008 at 03:27 PM

Speak your mind:

*

*


* I hate spam and will never sell or publish your email address.

(You may use textile in your comments.)

Subscribe

Browse by Tag