Skip to main content

Seven Useful ActiveModel Validators

|

Custom ActiveModel::Validators are an easy way to validate individual attributes on your Rails models. All that's required is a Ruby class that inherits from ActiveModel::EachValidator and implements a validate_each method that takes three arguments: record, attribute, and value. I have written a few lately, so I pinged the rest of the amazingly talented Viget developers for some contributions. Here's what we came up with.

Simple URI Validator

A "simple URI" can be either a relative path or an absolute URL. In this case, any value that could be parsed by Ruby's URI module is allowed:

<span class="k">class</span> <span class="nc">UriValidator</span> <span class="o"><</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">EachValidator</span>
 <span class="k">def</span> <span class="nf">validate_each</span><span class="p">(</span><span class="n">record</span><span class="p">,</span> <span class="n">attribute</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
 <span class="k">unless</span> <span class="n">valid_uri?</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
 <span class="n">record</span><span class="o">.</span><span class="n">errors</span><span class="o">[</span><span class="n">attribute</span><span class="o">]</span> <span class="o"><<</span> <span class="p">(</span><span class="n">options</span><span class="o">[</span><span class="ss">:message</span><span class="o">]</span> <span class="o">||</span> <span class="s1">'is not a valid URI'</span><span class="p">)</span>
 <span class="k">end</span>
 <span class="k">end</span>


 <span class="kp">private</span>

 <span class="k">def</span> <span class="nf">valid_uri?</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span>
 <span class="no">URI</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">uri</span><span class="p">)</span>
 <span class="kp">true</span>

 <span class="k">rescue</span> <span class="no">URI</span><span class="o">::</span><span class="no">InvalidURIError</span>
 <span class="kp">false</span>
 <span class="k">end</span>
<span class="k">end</span>

Full URL Validator

A "full URL" is defined as requiring a host and scheme. Ruby provides a regular expression to match against, so that's what is used in this validator:

<span class="k">class</span> <span class="nc">FullUrlValidator</span> <span class="o"><</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">EachValidator</span>
 <span class="no">VALID_SCHEMES</span> <span class="o">=</span> <span class="sx">%w(http https)</span>
 
 <span class="k">def</span> <span class="nf">validate_each</span><span class="p">(</span><span class="n">record</span><span class="p">,</span> <span class="n">attribute</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
 <span class="k">unless</span> <span class="n">value</span> <span class="o">=~</span> <span class="no">URI</span><span class="o">::</span><span class="n">regexp</span><span class="p">(</span><span class="no">VALID_SCHEMES</span><span class="p">)</span>
 <span class="n">record</span><span class="o">.</span><span class="n">errors</span><span class="o">[</span><span class="n">attribute</span><span class="o">]</span> <span class="o"><<</span> <span class="p">(</span><span class="n">options</span><span class="o">[</span><span class="ss">:message</span><span class="o">]</span> <span class="o">||</span> <span class="s1">'is not a valid URL'</span><span class="p">)</span>
 <span class="k">end</span>
 <span class="k">end</span>
<span class="k">end</span>

The Ruby regular expression can be seen as too permissive. For a stricter regular expression, Brian Landau shared this Github gist.

Email Validator

My good friends Lawson Kurtz and Mike Ackerman contributed the following email address validator:

<span class="k">class</span> <span class="nc">EmailValidator</span> <span class="o"><</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">EachValidator</span>
 <span class="k">def</span> <span class="nf">validate_each</span><span class="p">(</span><span class="n">record</span><span class="p">,</span> <span class="n">attribute</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
 <span class="k">unless</span> <span class="n">value</span> <span class="o">=~</span> <span class="sr">/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i</span>
 <span class="n">record</span><span class="o">.</span><span class="n">errors</span><span class="o">[</span><span class="n">attribute</span><span class="o">]</span> <span class="o"><<</span> <span class="p">(</span><span class="n">options</span><span class="o">[</span><span class="ss">:message</span><span class="o">]</span> <span class="o">||</span> <span class="s2">"is not a valid e-mail address"</span><span class="p">)</span>
 <span class="k">end</span>
 <span class="k">end</span>
<span class="k">end</span>

If you'd rather validate by performing a DNS lookup, Brian Landau has you covered with this Github gist.

Secure Password Validator

Lawson provided this secure password validator (though credit goes to former Viget developer, James Cook):

<span class="k">class</span> <span class="nc">SecurePasswordValidator</span> <span class="o"><</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">EachValidator</span>
 <span class="no">WORDS</span> <span class="o">=</span> <span class="no">YAML</span><span class="o">.</span><span class="n">load_file</span><span class="p">(</span><span class="s2">"config/bad_passwords.yml"</span><span class="p">)</span>

 <span class="k">def</span> <span class="nf">validate_each</span><span class="p">(</span><span class="n">record</span><span class="p">,</span> <span class="n">attribute</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
 <span class="k">if</span> <span class="n">value</span><span class="o">.</span><span class="n">in?</span><span class="p">(</span><span class="no">WORDS</span><span class="p">)</span>
 <span class="n">record</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">attribute</span><span class="p">,</span> <span class="s2">"is a common password. Choose another."</span><span class="p">)</span>
 <span class="k">end</span>
 <span class="k">end</span>
<span class="k">end</span>

Twitter Handle Validator

Lawson supplied this validator that checks for valid Twitter handles:

<span class="k">class</span> <span class="nc">TwitterHandleValidator</span> <span class="o"><</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">EachValidator</span>
 <span class="k">def</span> <span class="nf">validate_each</span><span class="p">(</span><span class="n">record</span><span class="p">,</span> <span class="n">attribute</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
 <span class="k">unless</span> <span class="n">value</span> <span class="o">=~</span> <span class="sr">/^[A-Za-z0-9_]{1,15}$/</span>
 <span class="n">record</span><span class="o">.</span><span class="n">errors</span><span class="o">[</span><span class="n">attribute</span><span class="o">]</span> <span class="o"><<</span> <span class="p">(</span><span class="n">options</span><span class="o">[</span><span class="ss">:message</span><span class="o">]</span> <span class="o">||</span> <span class="s2">"is not a valid Twitter handle"</span><span class="p">)</span>
 <span class="k">end</span>
 <span class="k">end</span>
<span class="k">end</span>

Hex Color Validator

A validator that's useful when an attribute should be a hex color value:

<span class="k">class</span> <span class="nc">HexColorValidator</span> <span class="o"><</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">EachValidator</span>
 <span class="k">def</span> <span class="nf">validate_each</span><span class="p">(</span><span class="n">record</span><span class="p">,</span> <span class="n">attribute</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
 <span class="k">unless</span> <span class="n">value</span> <span class="o">=~</span> <span class="sr">/\A([a-f0-9]{3}){,2}\z/i</span>
 <span class="n">record</span><span class="o">.</span><span class="n">errors</span><span class="o">[</span><span class="n">attribute</span><span class="o">]</span> <span class="o"><<</span> <span class="p">(</span><span class="n">options</span><span class="o">[</span><span class="ss">:message</span><span class="o">]</span> <span class="o">||</span> <span class="s1">'is not a valid hex color value'</span><span class="p">)</span>
 <span class="k">end</span>
 <span class="k">end</span>
<span class="k">end</span>

UPDATE: The regular expression has been simplified thanks to a comment from HappyNoff.

Regular Expression Validator

A great solution for attributes that should be a regular expression:

<span class="k">class</span> <span class="nc">RegexpValidator</span> <span class="o"><</span> <span class="no">ActiveModel</span><span class="o">::</span><span class="no">EachValidator</span>
 <span class="k">def</span> <span class="nf">validate_each</span><span class="p">(</span><span class="n">record</span><span class="p">,</span> <span class="n">attribute</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
 <span class="k">unless</span> <span class="n">valid_regexp?</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
 <span class="n">record</span><span class="o">.</span><span class="n">errors</span><span class="o">[</span><span class="n">attribute</span><span class="o">]</span> <span class="o"><<</span> <span class="p">(</span><span class="n">options</span><span class="o">[</span><span class="ss">:message</span><span class="o">]</span> <span class="o">||</span> <span class="s1">'is not a valid regular expression'</span><span class="p">)</span>
 <span class="k">end</span>
 <span class="k">end</span>


 <span class="kp">private</span>

 <span class="k">def</span> <span class="nf">valid_regexp?</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
 <span class="no">Regexp</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
 <span class="kp">true</span>

 <span class="k">rescue</span> <span class="no">RegexpError</span>
 <span class="kp">false</span>
 <span class="k">end</span>
<span class="k">end</span>

Bonus Round

Replace all of those default error messages above with I18n translated strings for great justice. For the Regular Expression Validator above, the validate_each method could look something like this:

<span class="k">def</span> <span class="nf">validate_each</span><span class="p">(</span><span class="n">record</span><span class="p">,</span> <span class="n">attribute</span><span class="p">,</span> <span class="n">value</span><span class="p">)</span>
 <span class="k">unless</span> <span class="n">valid_regexp?</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
 <span class="n">default_message</span> <span class="o">=</span> <span class="n">record</span><span class="o">.</span><span class="n">errors</span><span class="o">.</span><span class="n">generate_message</span><span class="p">(</span><span class="n">attribute</span><span class="p">,</span> <span class="ss">:invalid_regexp</span><span class="p">)</span>
 
 <span class="n">record</span><span class="o">.</span><span class="n">errors</span><span class="o">[</span><span class="n">attribute</span><span class="o">]</span> <span class="o"><<</span> <span class="p">(</span><span class="n">options</span><span class="o">[</span><span class="ss">:message</span><span class="o">]</span> <span class="o">||</span> <span class="n">default_message</span><span class="p">)</span>
 <span class="k">end</span>
<span class="k">end</span>

Then the following could be added to config/locales/en.yml:

en:
 errors:
 messages:
 invalid_regexp: is not a valid regular expression

Now the default error messages can be driven by I18n.

Conclusion

We've found these to be very helpful at Viget. What do you think? Which validators do you find useful? Are there others worth sharing? Please share in the comments below.

← Drag →