Validations For Non Activerecord Model Objects


layout: post title: Validations for non-ActiveRecord Model Objects —- Rails provides support for validating form input if the form is backed by an ActiveRecord. The application I am currently working on has a form that has a large number of input parameters but is not persisted to the database. I still wanted to use the ActiveRecord Validations as they make my life easier but I did not know if there was an simple way to do this.

Initially I created a dummy table in the database with just an id field and made my model object sub-class ActiveRecord. I could then use the validations with all the fields I had defined using attr\_accessor. This looked something like;

class Search < ActiveRecord::Base

attr\_accessor :user\_name, :email, :locator

validates\_length\_of :user\_name,
:within => 6..20,
:too\_long => pick a shorter name,
:too\_short => pick a longer name
validates\_format\_of :email,
:with => /^(\[^`\s]+)`((?:\[-a-z0-9\]*\\.)*\[a-z\]{2,})$/i
validates\_numericality\_of :locator

end

In my controller I created the Search object in the same way that I created all the other model objects but I never called save. Instead I called the valid? method to check whether the model passed all the validations. If the model is not valid the @search.errors object is populated with all the errors.

class NavigatorController < ApplicationController

def search
`search = Search.new(params[:search])
    if `search.valid?

end
end

end

Of course this left a bad taste in my mouth as it is a seriously ugly hack that requires an empty table in the database just to get form validation working. So I began to look at what I needed to do to implement an ActiveForm object. I was not looking forward to this task as I had read on the rails mailing list that the Validations were intermingled with ActiveRecord::Base and difficult to untangle.

This could not be further from the truth. The first thing I did was create a new ActiveForm class and include ActiveRecord::Validations. This caused a few errors as the ActiveRecord::Validations class attempts to call alias_method for methods that do not exist in ActiveForm. I implement these methods (save and update_attribute) so that they raise a NotImplementedError exception. Then I attempt to call the valid? method but it calls the new\_record? method which I implement to return true. To view the errors in the view using the standard helper methods I need to implement the human_attribute_name method. These changes seem to get basic validations working.

The only validations that are not working are validates\_uniqueness\_of and validates\_numericality\_of. validates\_uniqueness\_of is not expected to work as it accesses the database so I just make it raise a NotImplementedError exception. validates\_numericality\_of does not work as it relies on a method named “#{attr_name}_before_type_cast” for each attribute named “attr_name”. This is an artifact of the type coercion that ActiveRecord performs on input parameters. ActiveRecord will convert an input parameter from a string to an integer if the underlying database record stores the field as an integer. As this does not occur with ActiveForm I just duplicated the method and replaced “#{attr_name}_before_type_cast” with “#{attr_name}”.

The only functionality that ActiveForm was missing was the ability to create a model object from a hash. As ActiveForm does not need to do any type coercion this is as simple as

def initialize(attributes = nil)
if attributes
attributes.each do |key,value|
send(key.to\_s + =, value)
end
end
yield self if block\_given?
end

At this stage ActiveForm is in a usable state and it took less than 20 minutes. It only took that long because I needed to restart webrick for each change (not to mention the fact that I had never looked at ActiveRecord before). Isn’t ruby/rails great?

To get this working grab the active_form.rb file and place it in the app/models directory. You can then make your model objects extend ActiveForm and use them like regular ActiveRecord objects.

I cleaned up a few warts of ActiveForm like overriding methods you should not be calling (save, validate_on_create, validate_on_update). I hope to get motivated enough to send a patch that enables this style of functionality in the core once edge rails is working for me again.

Update:

It seems there is already a HowTo on the rails wiki that describes a similar technique. However rather than duplicating validates\_numericality\_of they handle the calls to “#{attr_name}_before_type_cast” by implementing a method_missing method which I incorporated to cleanup my code.

Update on 12th Dec 2005

Today I decided that I needed to add reloading of ActiveForm subclasses and this is done with the following code chunk.

require dispatcher
class Dispatcher
class << self
if ! method\_defined?(:form\_original\_reset\_application![]() 
      alias :form_original_reset_application) :reset\_application!
def reset\_application!
form\_original\_reset\_application!
Dependencies.remove\_subclasses\_for(ActiveForm) if defined?(ActiveForm)
end
end
end
end

Update 12th of May, 2010

The plugin is now available on GitHub. See the GitHub project page