Adam Speaks Out Of Turn

Posted on 2008/02/23

Contextual Validations With Datamapper

Validatable, Group Validations, and Validates_True_For

This entry reflects an older version of a library or code samples that may have expired since the time of it’s writing. Investigate the library or code samples further before use.

It’s a known fact that users are stupid. They screw up; it happens. They enter in wrong data, or leave things blank that shouldn’t be, or even enter in completely horrid data because they’re idiots and that’s what idiots do. I point you at youtube.com video comments, digg (as a whole), and myspace.com as proof of web users’ collective idiocy.

But, alas, they’re how we make our money online. Thus, we need to guard against idiots doing idiot things by validating anything that we need to save out to our persistence layers. Sometimes that means guarding against hack attempts, but most of the time it means guarding against invalid data.

Both ActiveRecord and DataMapper have a concept called Validations, which are ultimately callbacks which fire right before an object gets saved out to our persistence layer and interrupt things when it detects the work of idiots.

A problem arises when your website has users creating content and content being created automatically from scrapers or some sort of automated background process. (be it from RSS feeds or an ftp server or web-service or whatever). No idiots are involved in the creation of content when it’s imported into the system and you likely really want that content to appear in your system. This is where Group Validations come in to play.

Group Validations

Group Validations are callbacks which kick-in as a subset, rather than all validations running at once. You might want to make sure that a user enters the title for a blog post in your system, but you don’t really want such a check for when that blog post comes in off of your RSS scraping system. Maybe you’d send those imported blog posts into a holding pen somewhere so that they can be rescued later, rather than preventing their save and never importing them in at all.

With ActiveRecord, if you declare a validates_presence_of on :title, that’s it, game over. The only way to bypass that validation is to .save_without_validations and that skips all of your validations, rather than just this one.

But with DataMapper and it’s use of Validatable, you can check for the validity of an object depending on the circumstance you’re in. Here’s what that blog post model would look like if we wanted to validate blog posts by idiots, but not from our not-so-idiotic scrapper:

If this shit doesn’t work, consider it pseudo-code. If it does work, I’m a badass

 1 class Post
 2   include DataMapper::Persistence
 3 
 4   property :title, :string, :length => 0..255
 5   property :body, :text
 6   property :original_uri, :string, :length => 0..255
 7   property :created_at, :datetime
 8   property :can_be_displayed, :boolean, :default => false
 9 
10   # user creation
11   validates_presence_of :title,
12     :groups => [:manual_entry, :display]
13   validates_presence_of :body,
14     :groups => [:manual_entry, :save, :display]
15 
16   # automated import
17   validates_presence_of :original_uri, :groups => [:import]
18 
19   alias_method :__save, :save
20 
21   def save(context = :valid_for_save?)
22     self.__save if self.send(context)
23   end
24 
25   before_save do |instance|
26     instance.can_be_displayed = true if instance.valid_for_display?
27   end
28 
29 end
30 

Running quickly through my sample here, you’ll spot this odd :groups => [...] argument to a few of the validations. These define which group these validations are a part of. Validatable uses these to give us a few dynamic methods like .valid_for_display? and .valid_for_manual_entry?, which is the mechanism used to check if an instance is valid in one context or another.

Using a model setup like this, we could call @post.valid_for_manual_entry? when we need to verify that the idiot’s blog post can be added into our persistence layer safely. By overloading the .save() method so that you pass in the group of validations to be executed (like :valid_for_manual_entry or :valid_for_import) to use when checking validity, we’ve effectively made it possible to choose which validation callbacks get fired and which don’t when saving out the model. NOTE: as I was writing this, discussion was occurring in #datamapper on irc.freenode.net about making .save() smarter so it respects grouping. Having to overload .save() may not be needed in the future. As usual, your results may vary.

You’ll notice that I gave :body a validates_presence_of for both the :manual_entry group and the :save group. This means that, no matter what, that validation callback will kick in.

Also of note is the can_be_displayed boolean and the before_save manual callback I defined. Here, I’m helping myself out later on so that it’s easy to pull out valid blog posts that can be displayed without worrying about nil field values and such:

1 @posts = Post.all(
2   :title.not => nil,
3   :slug.not => nil,
4   :order => "created_at desc",
5   :limit => 10
6 )
7 

Becomes…

1 @posts = Post.all(
2   :can_be_displayed => true,
3   :order => "created_at desc",
4   :limit => 10
5 )

Pretty sexy, no? I can’t off-hand think of a way to get this functionality from ActiveRecord objects without manually mixing in Validatable and then fighting the battle between AR’s validations and Validatable’s validations. (I likely just need to think harder, though….maybe using single-table inheritance and then tacking on different validations for different subclasses….maybe?)

With the proper use of Group Validations, you end up saving yourself a lot of headache and work later on down the line, as well as supporting different scenarios where a post might be valid or might not…all without having to hack-around. How enterprise-y!

validates_true_for

The second outstanding feature of Validatable that I’m oh-so-in-love with is validates_true_for. Think of it like overloading valid? only capable of the full power of real validations behind it.

Say, for example, you’ve got an Event model that needs to make sure the end_date for the event is greater than the start_date. Wouldn’t want to break the laws of physics, so we’d do something like:

1 class Event < ActiveRecord::Base
2 
3   def valid?
4     start_time < end_time
5   end
6 
7 end

Yup, it’s pretty simple with ActiveRecord. Just toss in our own valid? method and we’re done. With DataMapper, things are a touch more complicated, but overall not brutally difficult, and buy you the full power of Validatable validations:

 1 class Event
 2   include DataMapper::Persistence
 3 
 4   # properties here
 5 
 6   validates_true_for :start_time, :logic => lambda {
 7     start_time < end_time
 8   }
 9 end
10 

So, a couple of things are going on here. First, we’re declaring the check to make sure start_time being less than end_time on the start_time property. We could have easily done it on the end_time property as well (take your pick). Secondly, we’re passing in a block (lambda) to be called when the check occurs. As long as our lambda returns true, we’re golden, the validation passes, and the object can be saved out to the persistence layer.

Say we want to do much more complicated logic, though.

 1 class Event
 2   include DataMapper::Persistence
 3 
 4   # properties here
 5 
 6   scary_validation = lambda do
 7     # freakish logic here for particularly complicated
 8     # validations.
 9   end
10 
11   validates_true_for :start_time, :logic => scary_validation
12 
13 end

Ruby’s support for closures is so damn-skippy that we can pass blocks of code around, assign them to variables, execute them later, totally forget about them, whatever we want. We don’t need to overload a method or anything.

Plus, we’ve elevated our advanced validation logic into a real Validation which we can then assign to groups, pass around from object to object, what-have-you. Had we just stuck this code in an overloaded .valid? method, we wouldn’t get our spiffy Group Validation stuff as well as a few other things that make Validatable so-very-sexy.

In Conclusion

Validations with Validatable (in DataMapper) are that much more powerful than their counter-parts in ActiveRecord, and therefore, you should switch to DataMapper (or Validatable) ;-)