Updated Validateable Recipe
Recipe 64[1] shows how to use Rails’ ActiveRecord Validations functionality on objects that don’t have corresponding database records. The canonical example of this situation is a Password
(model) class. It helps support the view but there is no actual passwords
table in the database.
When I went to use the recipe, I ran into an issue that others have seen. Turns out that Rails has changed since the recipe was written and now the validation functionality is expecting a validateable class to define a class method called human_attribute_name
. Looking back at my old projects I notice that this has been the case since at least Spring 2006. Whatever. I tweaked the recipe and now it works w/ my fairly edgy Rails version (I’m on revision 5662 just now). Here’s the code:
<span class="linenum"> 1</span> <span class="source source_ruby"><span class="declaration declaration_module declaration_module_ruby"><span class="keyword keyword_control keyword_control_module keyword_control_module_ruby">module</span> <span class="entity entity_name entity_name_module entity_name_module_ruby">Validateable</span></span>
<span class="linenum"> 2 </span>[<span class="constant constant_other constant_other_symbol constant_other_symbol_ruby">:save</span>, <span class="constant constant_other constant_other_symbol constant_other_symbol_ruby">:save!</span>, <span class="constant constant_other constant_other_symbol constant_other_symbol_ruby">:update_attribute</span>].each{|<span class="keyword keyword_other keyword_other_special-method keyword_other_special-method_ruby">attr</span>| define_method(<span class="keyword keyword_other keyword_other_special-method keyword_other_special-method_ruby">attr</span>){}}
<span class="linenum"> 3</span><span class="declaration declaration_function declaration_function_method declaration_function_method_with-arguments declaration_function_method_with-arguments_ruby"> <span class="keyword keyword_control keyword_control_def keyword_control_def_ruby">def</span> <span class="entity entity_name entity_name_function entity_name_function_ruby">method_missing</span>(<span class="variable variable_parameter">symbol, *params</span>)</span>
<span class="linenum"> 4</span> <span class="keyword keyword_control keyword_control_ruby">if</span>(symbol.to_s =~<span class="string string_regexp string_regexp_classic string_regexp_classic_ruby"> /<span class="string string_regexp string_regexp_group string_regexp_group_ruby">(.*)</span>_before_type_cast$/</span>)
<span class="linenum"> 5</span> send(<span class="variable variable_other variable_other_readwrite variable_other_readwrite_global variable_other_readwrite_global_pre-defined variable_other_readwrite_global_pre-defined_ruby">$1</span>)
<span class="linenum"> 6</span> <span class="keyword keyword_control keyword_control_ruby">end</span>
<span class="linenum"> 7</span> <span class="keyword keyword_control keyword_control_ruby">end</span>
<span class="linenum"> 8 </span><span class="declaration declaration_module declaration_module_ruby"><span class="keyword keyword_control keyword_control_module keyword_control_module_ruby">module</span> <span class="entity entity_name entity_name_module entity_name_module_ruby">ClassMethods</span></span>
<span class="linenum"> 9 </span><span class="declaration declaration_function declaration_function_method declaration_function_method_with-arguments declaration_function_method_with-arguments_ruby"><span class="keyword keyword_control keyword_control_def keyword_control_def_ruby">def</span> <span class="entity entity_name entity_name_function entity_name_function_ruby">human_attribute_name</span>(<span class="variable variable_parameter">attribute_key_name</span>)</span>
<span class="linenum"> 10 </span>attribute_key_name.humanize
<span class="linenum"> 11</span> <span class="keyword keyword_control keyword_control_ruby">end</span>
<span class="linenum"> 12</span> <span class="keyword keyword_control keyword_control_ruby">end</span>
<span class="linenum"> 13</span><span class="declaration declaration_function declaration_function_method declaration_function_method_with-arguments declaration_function_method_with-arguments_ruby"> <span class="keyword keyword_control keyword_control_def keyword_control_def_ruby">def</span> <span class="entity entity_name entity_name_function entity_name_function_ruby">self.included</span>(<span class="variable variable_parameter">base</span>)</span>
<span class="linenum"> 14 </span>base.send(<span class="constant constant_other constant_other_symbol constant_other_symbol_ruby">:include</span>, <span class="variable variable_other variable_other_constant variable_other_constant_ruby">ActiveRecord</span>::<span class="variable variable_other variable_other_constant variable_other_constant_ruby">Validations</span>)
<span class="linenum"> 15</span> base.extend(<span class="variable variable_other variable_other_constant variable_other_constant_ruby">ClassMethods</span>)
<span class="linenum"> 16 </span><span class="keyword keyword_control keyword_control_ruby">end</span>
<span class="linenum"> 17</span> <span class="keyword keyword_control keyword_control_ruby">end</span></span>
Just put that code into validateable.rb
, drop it into your lib
directory and put an include Validateable
line at the top (inside) of your class definition.
Notice that instead of implementing the append_features
class method (as the original recipe did), I’m implementing included
as recommended in the Module#append_features documentation[2]. It’s just a little cleaner in that it eliminates the need to invoke super
.
One last interesting bit is that I ran into difficulties convincing Ruby to add a class method to the target class. At first I tried simply using this definition:
<span class="linenum"> 1</span> <span class="source source_ruby"><span class="declaration declaration_function declaration_function_method declaration_function_method_with-arguments declaration_function_method_with-arguments_ruby"><span class="keyword keyword_control keyword_control_def keyword_control_def_ruby">def</span> <span class="entity entity_name entity_name_function entity_name_function_ruby">self.human_attribute_name</span>(<span class="variable variable_parameter">attribute_key_name</span>)</span>
</span>
In place of the sub-module ClassMethods
and the base.extend...
call. I still don’t fully get why that approach wouldn’t work, but after reading Jay Fields’ post on Ruby: instance and class methods from a module I thought I’d give this approach a try — and voilà .
[1] Recipe 64 Validating Non-Active Record Objects in Chad Fowler’s Rails Recipes.
[2] p. 554 of Dave Thomas’ Programming Ruby a.k.a. The Pickaxe Book.