Validations

DataMapper validations allow you to vet data prior to saving to a database. To make validations available to your app you simply 'require "dm-validations"' in your application. With DataMapper there are two different ways you can validate your classes' properties.

Manual Validation

Much like a certain other Ruby ORM we can call validation methods directly by passing them a property name (or multiple property names) to validate against.

1   validates_length_of :name
2   validates_length_of :name, :description

These are the currently available manual validations. Please refer to the API docs for more detailed information.

Auto-Validations

By adding triggers to your property definitions you can both define and validate your classes properties all in one fell swoop.

Triggers that generate validator creation:

  # implicitly creates a validates_presence_of
  :required => true  # cannot be nil

  # implicitly creates a (scoped) validates_uniqueness_of
  # a symbol value (or an array of symbols) must denote
  # one or more of the resource's properties and will
  # be passed on as the :scope option to validates_uniqueness
  :unique => true            # must be unique
  :unique => :some_scope     # must be unique within some_scope
  :unique => [:some, :scope] # must be unique within [:some, :scope]

  # implicitly creates a validates_length_of
  :length => 0..20  # must be between 0 and 20 characters in length
  :length => 1..20  # must be between 1 and 20 characters in length

  # implicitly creates a validates_format_of
  :format => :email_address  # predefined regex
  :format => :url            # predefined regex
  :format => /\w+_\w+/
  :format => lambda { |str| str }
  :format => proc { |str| str }
  :format => Proc.new { |str| str }

Here we see an example of a class with both a manual and auto-validation declared:

 1   require 'dm-validations'
 2 
 3   class Account
 4     include DataMapper::Resource
 5 
 6     property :name, String
 7 
 8     # good old fashioned manual validation
 9     validates_length_of :name, :max => 20
10 
11     property :content, Text, :length => 100..500
12   end

Validating

DataMapper validations, when included, alter the default save/create/update process for a model.

You may manually validate a resource using the valid? method, which will return true if the resource is valid, and false if it is invalid.

Working with Validation Errors

If your validators find errors in your model, they will populate the Validate::ValidationErrors object that is available through each of your models via calls to your model's errors method.

1   my_account = Account.new(:name => 'Jose')
2   if my_account.save
3     # my_account is valid and has been saved
4   else
5     my_account.errors.each do |e|
6       puts e
7     end
8   end

Error Messages

The error messages for validations provided by DataMapper are generally clear, and explain exactly what has gone wrong. If they're not what you want though, they can be changed. This is done via providing a :message in the options hash, for example:

  validates_uniqueness_of :title, :scope => :section_id,
    :message => "There's already a page of that title in this section"

This example also demonstrates the use of the :scope option to only check the property's uniqueness within a narrow scope. This object won't be valid if another object with the same @section_id@ already has that title.

Something similar can be done for auto-validations, too, via setting :messages in the property options.

  property :email, String, :required => true, :unique => true,
    :format   => :email_address,
    :messages => {
      :presence  => "We need your email address.",
      :is_unique => "We already have that email.",
      :format    => "Doesn't look like an email address to me ..."
    }

To set an error message on an arbitrary field of the model, DataMapper provides the add command.

1   @resource.errors.add(:title, "Doesn't mention DataMapper")

This is probably of most use in custom validations, so ...

Custom Validations

DataMapper provides a number of validations for various common situations such as checking for the length or presence of strings, or that a number falls in a particular range. Often this is enough, especially when validations are combined together to check a field for a number of properties. For the situations where it isn't, DataMapper provides a couple of methods: validates_with_block and validates_with_method. They're very similar in operation, with one accepting a block as the argument and the other taking a symbol representing a method name.

The method or block performs the validation tests and then should return true if the resource is valid or false if it is invalid. If the resource isn't valid instead of just returning false, an array containing false and an error message, such as [ false, 'FAIL!' ] can be returned. This will add the message to the errors on the resource.

 1   class WikiPage
 2     include DataMapper::Resource
 3 
 4     # properties ...
 5 
 6     validates_with_method :check_citations
 7 
 8     # checks that we've included at least 5 citations for our wikipage.
 9     def check_citations
10       # in a 'real' example, the number of citations might be a property set by
11       # a before :valid? hook.
12       num = count_citations(self.body)
13       if num > 4
14         return true
15       else
16         [ false, "You must have at least #{5 - num} more citations for this article" ]
17       end
18     end
19   end

Instead of setting an error on the whole resource, you can set an error on an individual property by passing this as the first argument to validates_with_block or validates_with_method. To use the previous example, replacing line 5 with:

  validates_with_method :body, :method => :check_citations

This would result in the citations error message being added to the error messages for the body, which might improve how it is presented to the user.

Conditional Validations

Validations don't always have to be run. For example, an issue tracking system designed for git integration might require a commit identifier for the fix--but only for a ticket which is being set to 'complete'. A new, open or invalid ticket, of course, doesn't necessarily have one. To cope with this situation and others like it, DataMapper offers conditional validation, using the :if and :unless clauses on a validation.

:if and :unless take as their value a symbol representing a method name or a Proc. The associated validation will run only if (or unless) the method or Proc returns something which evaluates to true. The chosen method should take no arguments, whilst the Proc will be called with a single argument, the resource being validated.

 1   class Ticket
 2     include DataMapper::Resource
 3 
 4     property :id,          Serial
 5     property :title,       String, :required => true
 6     property :description, Text
 7     property :commit,      String
 8     property :status,      Enum[ :new, :open, :invalid, :complete ]
 9 
10     validates_presence_of :commit, :if => lambda { |t| t.status == :complete }
11   end

The autovalidation that requires the title to be present will always run, but the validates_presence_of on the commit hash will only run if the status is :complete. Another example might be a change summary that is only required if the resource is already there--'initial commit' is hardly an enlightening message.

  validates_length_of :change_summary, :min => 10, :unless => :new?

Sometimes a simple on and off switch is not enough, and so ...

Contextual Validations

DataMapper Validations also provide a means of grouping your validations into contexts. This enables you to run different sets of validations under different contexts. All validations are performed in a context, even the auto-validations. This context is the :default context. Unless you specify otherwise, any validations added will be added to the :default context and the valid? method checks all the validations in this context.

One example might be differing standards for saving a draft version of an article, compared with the full and ready to publish article. A published article has a title, a body of over 1000 characters, and a sidebar picture. A draft article just needs a title and some kind of body. The length and the sidebar picture we can supply later. There's also a published property, which is used as part of queries to select articles for public display.

To set a context on a validation, we use the :when option. It might also be desirable to set :auto_validation => false on the properties concerned, especially if we're messing with default validations.

 1   class Article
 2     include DataMapper::Resource
 3 
 4     property :id,          Serial
 5     property :title,       String
 6     property :picture_url, String
 7     property :body,        Text
 8     property :published,   Boolean
 9 
10     # validations
11     validates_presence_of :title,       :when => [ :draft, :publish ]
12     validates_presence_of :picture_url, :when => [ :publish ]
13     validates_presence_of :body,        :when => [ :draft, :publish ]
14     validates_length_of   :body,        :when => [ :publish ], :minimum => 1000
15     validates_absence_of  :published,   :when => [ :draft ]
16   end
17 
18   # and now some results
19   @article = Article.new
20 
21   @article.valid?(:draft)
22   # => false.  We have no title, for a start.
23 
24   @article.valid_for_publish?
25   # => false.  We have no title, amongst many other issues.
26   # valid_for_publish? is provided shorthand for valid?(:publish)
27 
28   # now set some properties
29   @article.title = 'DataMapper is awesome because ...'
30   @article.body  = 'Well, where to begin ...'
31 
32   @article.valid?(:draft)
33   # => true.  We have a title, and a little body
34 
35   @article.valid?(:publish)
36   # => false.  Our body isn't long enough yet.
37 
38   # save our article in the :draft context
39   @article.save(:draft)
40   # => true
41 
42   # set some more properties
43   @article.picture_url = 'http://www.greatpictures.com/flower.jpg'
44   @article.body        = an_essay_about_why_datamapper_rocks
45 
46   @article.valid?(:draft)
47   # => true.  Nothing wrong still
48 
49   @article.valid?(:publish)
50   # => true.  We have everything we need for a full article to be published!
51 
52   @article.published = true
53 
54   @article.save(:draft)
55   # => false.  We set the published to true, so we can't save this as a draft.
56   # As long as our drafting method always saves with the :draft context, we won't ever
57   # accidentally save a half finished draft that the public will see.
58 
59   @article.save(:publish)
60   # => true
61   # we can save it just fine as a published article though.

That was a long example, but it shows how to set up validations in differing contexts and also how to save in a particular context. One thing to be careful of when saving in a context is to make sure that any database level constraints, such as a NOT NULL column definition in a database, are checked in that context, or a data-store error may ensue.

Setting Properties Before Validation

It is sometimes necessary to set properties before a resource is saved or validated. Perhaps a required property can have a default value set from other properties or derived from the environment. To set these properties, a before :valid? hook should be used.

 1   class Article
 2     include DataMapper::Resource
 3 
 4     property :id,        Serial
 5     property :title,     String, :required => true
 6     property :permalink, String, :required => true
 7 
 8     before :valid?, :set_permalink
 9 
10     # our callback needs to accept the context used in the validation,
11     # even if it ignores it, as #save calls #valid? with a context.
12     def set_permalink(context = :default)
13       self.permalink = title.gsub(/\s+/, '-')
14     end
15   end

Be careful not to save your resource in these kinds of methods, or your application will spin off into infinite trying to save your object while saving your object.