RealityForge.org: Validations for non-ActiveRecord Model Objects /articles/2005/12/02/validations-for-non-activerecord-model-objects en-us 40 A little short for a storm trooper Validations for non-ActiveRecord Model Objects <p>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 <a href="http://api.rubyonrails.com/classes/ActiveRecord/Validations.html">Validations</a> as they make my life easier but I did not know if there was an simple way to do this.</p> <p>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 <code>attr_accessor</code>. This looked something like;</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">Search</span> <span class="punct">&lt;</span> <span class="constant">ActiveRecord</span><span class="punct">::</span><span class="constant">Base</span> <span class="ident">attr_accessor</span> <span class="symbol">:user_name</span><span class="punct">,</span> <span class="symbol">:email</span><span class="punct">,</span> <span class="symbol">:locator</span> <span class="ident">validates_length_of</span> <span class="symbol">:user_name</span><span class="punct">,</span> <span class="symbol">:within</span> <span class="punct">=&gt;</span> <span class="number">6</span><span class="punct">..</span><span class="number">20</span><span class="punct">,</span> <span class="symbol">:too_long</span> <span class="punct">=&gt;</span> <span class="punct">&quot;</span><span class="string">pick a shorter name</span><span class="punct">&quot;,</span> <span class="symbol">:too_short</span> <span class="punct">=&gt;</span> <span class="punct">&quot;</span><span class="string">pick a longer name</span><span class="punct">&quot;</span> <span class="ident">validates_format_of</span> <span class="symbol">:email</span><span class="punct">,</span> <span class="symbol">:with</span> <span class="punct">=&gt;</span> <span class="punct">/</span><span class="regex">^([^@<span class="escape">\s</span>]+)@((?:[-a-z0-9]+<span class="escape">\.</span>)+[a-z]{2,})$</span><span class="punct">/</span><span class="ident">i</span> <span class="ident">validates_numericality_of</span> <span class="symbol">:locator</span> <span class="punct">...</span> <span class="keyword">end</span></code></pre></div> <p>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 <code>valid?</code> method to check whether the model passed all the validations. If the model is not valid the <code>@search.errors</code> object is populated with all the errors.</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">NavigatorController</span> <span class="punct">&lt;</span> <span class="constant">ApplicationController</span> <span class="keyword">def </span><span class="method">search</span> <span class="attribute">@search</span> <span class="punct">=</span> <span class="constant">Search</span><span class="punct">.</span><span class="ident">new</span><span class="punct">(</span><span class="ident">params</span><span class="punct">[</span><span class="symbol">:search</span><span class="punct">])</span> <span class="keyword">if</span> <span class="attribute">@search</span><span class="punct">.</span><span class="ident">valid?</span> <span class="punct">...</span> <span class="keyword">end</span> <span class="keyword">end</span> <span class="punct">...</span> <span class="keyword">end</span></code></pre></div> <p>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 <b>ActiveForm</b> 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.</p> <p>This could not be further from the truth. The first thing I did was create a new ActiveForm class and <code>include ActiveRecord::Validations</code>. 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 <code>valid?</code> method but it calls the <code>new_record?</code> 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.</p> <p>The only validations that are not working are <code>validates_uniqueness_of</code> and <code>validates_numericality_of</code>. <code>validates_uniqueness_of</code> is not expected to work as it accesses the database so I just make it raise a NotImplementedError exception. <code>validates_numericality_of</code> does not work as it relies on a method named &#8221;#{attr_name}_before_type_cast&#8221; for each attribute named &#8220;attr_name&#8221;. 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 &#8221;#{attr_name}_before_type_cast&#8221; with &#8221;#{attr_name}&#8221;.</p> <p>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</p> <div class="typocode"><pre><code class="typocode_ruby "> <span class="keyword">def </span><span class="method">initialize</span><span class="punct">(</span><span class="ident">attributes</span> <span class="punct">=</span> <span class="constant">nil</span><span class="punct">)</span> <span class="keyword">if</span> <span class="ident">attributes</span> <span class="ident">attributes</span><span class="punct">.</span><span class="ident">each</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">key</span><span class="punct">,</span><span class="ident">value</span><span class="punct">|</span> <span class="ident">send</span><span class="punct">(</span><span class="ident">key</span><span class="punct">.</span><span class="ident">to_s</span> <span class="punct">+</span> <span class="punct">'</span><span class="string">=</span><span class="punct">',</span> <span class="ident">value</span><span class="punct">)</span> <span class="keyword">end</span> <span class="keyword">end</span> <span class="keyword">yield</span> <span class="constant">self</span> <span class="keyword">if</span> <span class="ident">block_given?</span> <span class="keyword">end</span></code></pre></div> <p>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&#8217;t ruby/rails great?</p> <p>To get this working grab the <a href="/files/active_form.rb">active_form.rb</a> 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.</p> <p>I cleaned up a few warts of ActiveForm like overriding methods you should not be calling (save!, save_with_validation, create!, 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.</p> <p><b>Update:</b></p> <p>It seems there is already a <a href="http://wiki.rubyonrails.com/rails/pages/HowToUseValidationsWithoutExtendingActiveRecord">HowTo</a> on the rails wiki that describes a similar technique. However rather than duplicating <code>validates_numericality_of</code> they handle the calls to &#8221;#{attr_name}_before_type_cast&#8221; by implementing a <a href="http://www.rubycentral.com/ref/ref_c_object.html#method_missing">method_missing</a> method which I incorporated to cleanup my code.</p> <p><b>Update on 12th Dec 2005</b></p> <p>Today I decided that I needed to add reloading of ActiveForm subclasses and this is done with the following code chunk.</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="ident">require</span> <span class="punct">'</span><span class="string">dispatcher</span><span class="punct">'</span> <span class="keyword">class </span><span class="class">Dispatcher</span> <span class="keyword">class </span><span class="punct">&lt;&lt;</span> <span class="constant">self</span> <span class="keyword">if</span> <span class="punct">!</span> <span class="ident">method_defined?</span><span class="punct">(</span><span class="symbol">:form_original_reset_application!</span><span class="punct">)</span> <span class="keyword">alias</span> <span class="symbol">:form_original_reset_application!</span> <span class="symbol">:reset_application!</span> <span class="keyword">def </span><span class="method">reset_application!</span> <span class="ident">form_original_reset_application!</span> <span class="constant">Dependencies</span><span class="punct">.</span><span class="ident">remove_subclasses_for</span><span class="punct">(</span><span class="constant">ActiveForm</span><span class="punct">)</span> <span class="keyword">if</span> <span class="keyword">defined?</span><span class="punct">(</span><span class="constant">ActiveForm</span><span class="punct">)</span> <span class="keyword">end</span> <span class="keyword">end</span> <span class="keyword">end</span> <span class="keyword">end</span></code></pre></div> <p><b>Download:</b> <a href="/files/active_form.rb">active_form.rb</a></p> <p><b>Update on 1st March 2006</b></p> <p>Available as a plugin at</p> <p>svn: <a href="http://www.realityforge.org/svn/code/active-form/trunk">http://www.realityforge.org/svn/code/active-form/trunk</a></p> Fri, 02 Dec 2005 22:48:00 +1100 urn:uuid:ab47f804c7ed2a38c4c29d82f56c5548 Peter Donald http://www.realityforge.org/articles/2005/12/02/validations-for-non-activerecord-model-objects Rails http://www.realityforge.org/articles/trackback/9 "Validations for non-ActiveRecord Model Objects" by Peter <p>There is also an article <a href="http://rails.techno-weenie.net/tip/2005/11/19/validate_your_forms_with_a_table_less_model" rel="nofollow">Validate your forms with a table-less model</a> by Rick Olson that may be useful</p> Tue, 06 Dec 2005 12:36:16 +1100 urn:uuid:c31fb5ce-a33a-42db-ac70-0184df38055b http://www.realityforge.org/articles/2005/12/02/validations-for-non-activerecord-model-objects#comment-17 "Validations for non-ActiveRecord Model Objects" by Peter <p>I went to create a wiki page on this and found that there is already a <a href="http://wiki.rubyonrails.com/rails/pages/HowToUseValidationsWithoutExtendingActiveRecord" rel="nofollow">HowTo</a> so I may just update that page with some more details.</p> Tue, 06 Dec 2005 11:12:20 +1100 urn:uuid:0dc10f7e-b46c-43b0-9d54-6eff77fb63d3 http://www.realityforge.org/articles/2005/12/02/validations-for-non-activerecord-model-objects#comment-16 "Validations for non-ActiveRecord Model Objects" by Martin <p>Followed your link from the Ruby on Rails mailing list digest. You shoud add a link to your code on the rubyonrails wiki if you haven&#8217;t done so already. Very handy &#8211; thanks Peter!</p> Sun, 04 Dec 2005 03:20:03 +1100 urn:uuid:210b00c4-680b-447f-a4d0-f1005ca34de5 http://www.realityforge.org/articles/2005/12/02/validations-for-non-activerecord-model-objects#comment-13