Sunday, November 04, 2007

Validatable 1.6.6 released

The 1.6.6 version of Validatable was released this morning.

This was a minor release that removed custom validation assertion syntax. (http://blog.jayfields.com/2007/09/ruby-creating-custom-assertions.html)

You can replace custom validation assertions with the newly introduced validate_only instance method. The validate_only method is my latest attempt to make it easy to test model validations one at a time.

The following code should provide a decent example.

require 'rubygems'
require 'test/unit'
require 'dust'
require 'validatable'

class SampleModel
include Validatable
validates_presence_of :name, :address
attr_accessor :name, :address
end

unit_tests do
test "individual validation for name is executed" do
instance = SampleModel.new
instance.validate_only("presence_of/name")
assert_equal "can't be empty", instance.errors.on(:name)
end

test "individual validation for address is not executed" do
instance = SampleModel.new
instance.validate_only("presence_of/name")
assert_equal nil, instance.errors.on(:address)
end
end


The validate_only method accepts an argument that is a DSL for running a single validation. The format of the DSL is "[validation type]/[key or attribute]". If the validation has a key set you should provide the key, otherwise the attribute will suffice. The documentation provides the following examples.
  • validates_presence_of :name can be run with obj.validate_only("presence_of/name")
  • validates_presence_of :birthday, :key => "a key" can be run with obj.validate_only("presence_of/a key")

9 comments:

  1. Jay, have you thought about adding support for contextual-validations?

    I do something like:

    validates_prescense_of :account_number, :on => :save
    validates_presence_of :login

    That way I can do something like:
    u =User.new(:login => 'bob')
    u.valid? => true
    u.valid?(:save) => false

    The point is that I often use models in multiple roles, not just within the web-app, so it makes sense that the business rules for an import script to pull legacy users from a CSV would have different business rules than the user edit form on an admin page.

    So... my whole point is, if you're open to it, I'd love to ditch my own validation code in DataMapper and go with yours if you were open to the idea of adding context for validations (beyond Rails' limited :create and :update contexts).

    ReplyDelete
  2. Sam,

    Validatable already has support for contextual-validations.

    What you want to do can be accomplished using groups.

    This entry contains an example of how to use groups.

    Using your example, you could write:

    class User
    validates_presence_of :login
    validates_presence_of :account_number, :groups => :save
    end

    u = User.new(:login => 'bob')
    u.valid? # => true
    u.valid_for_saving? # => false

    I'm definitely open to improving the contextual validations if you find them inadequate.

    Cheers, Jay

    ReplyDelete
  3. Jay, that's pretty cool. Any way I can convince you to drop by #datamapper on IRC sometime and we can discuss this a little more? I'd love to rip out my own validations and insert a dependency on your gem instead.

    One last concern: I'm using a String#t no-op to allow for translated versions if you implement String#translate. Would you consider adding support for the same? It's such a small coding challenge, I really want to make it easy for international users to take advantage of DM without having to hack away at the internals.

    Feel free to shoot me an email or IM if you're open to any of this. :)

    ReplyDelete
  4. Hey,

    Now if I need to verify validates_presence_of on an attribute using rspec, I need to say something like:

    @email.validate_only(
    "presence_of/body") @email.errors.on(:body).
    should_not be_nil

    Do you think it's valueable to add some methods like has_error_on into Validatable so that I can say something like

    @email.should have_error_on(:body)

    Thanks

    ReplyDelete
  5. Hey Yi,

    I'm not sure.

    @email.should have_error_on(:body)

    reads nicely; however, it only makes sense in the context of RSpec

    @email.has_error_on?(:body)

    reads nicely in general; however it causes the RSpec version to become

    @email.should has_error_on?(:body)

    which is not so nice.

    I'm leaning towards has_error_on?

    Thoughts?

    ReplyDelete
  6. Hey Jay,

    Thanks for the reply. If you have method has_something on obj, you can say obj.should have_something in Rspec. It's a built-in feature of Rspec. so if you put has_error_on, in Rspec I can say email.should have_error_on, which is perfect.

    ReplyDelete
  7. Actually, thinking twice, maybe it's not a good idea indeed. Because I can say

    active_record_model.should
    have(3).errors_on(:attribute)

    This statement will do valid? and check the error for you.

    So maybe a better way is to make a plugin connecting Rspec to Valitable so the same syntax can be applied on a validatable object as well. I will make this happen in my project. thanks

    ReplyDelete
  8. I did wrote an extension so that I can say:
    email.should
    have(1).error_on_presence_of(:body)

    it looks pretty neat.

    The only thing is that I still cannot say:
    email.should
    have(:no).errors_on_presence_of(:attachment)

    because validate_only('presence_of/attachment')
    will raise an ArgumentError if I don't declare
    validates_presence_of :attachment.

    ReplyDelete
  9. Brad Carson7:06 PM

    Validatable is here for anyone looking for it:
    http://validatable.rubyforge.org/

    ReplyDelete

Note: Only a member of this blog may post a comment.