Validation with Aggregation

ActiveRecord supports composed_of for value objects which is
fantastic but one thing that it doesn’t seem to support (or at least
I am unable to find any documentation for) validation of the value
objects.

For example, given the following:

class Message < ActiveRecord::Base
composed_of :sender, :class_name => ‘EmailAddress’
composed_of :recipient, :class_name => ‘EmailAddress’

end

class EmailAddress
attr_reader :name, :address
def initialize(name, address)
@name, @address = name, address
end

end

How can I best take advantage of Rails’ validation, given that I’d
like to only specify the validation rules for EmailAddress once. The
validation rules might look something like:

validates_presence_of :name, :address
validates_format_of :email, :with => ADDRESS_FORMAT

I’m guessing the obvious answer is to create an email_addresses table
and use belongs_to but that seems (to me at least) a somewhat heave-
handed approach.

Regards,

Simon


Simon Harris
RedHill Consulting, Pty. Ltd.
12/55-67 Batman Street
West Melbourne VIC Australia 3003
http://www.redhillconsulting.com.au
mob: +61 417 505 611
yahoo/msn/skype: haruki_zaemon
gmail: haruki.zaemon
icq: 20461518

Simon Harris wrote:

ActiveRecord supports composed_of for value objects which is
fantastic but one thing that it doesn’t seem to support (or at least
I am unable to find any documentation for) validation of the value
objects.

I’m a rails newbie struggling with the same issue. Here’s what I came
up with:

  1. Grab the ActiveForm class described here:
    http://www.realityforge.org/articles/2005/12/02/validations-for-non-activerecord-model-objects

The code is here:

http://www.realityforge.org/files/active_form.rb

I used it pretty much as is, but renamed it from ActiveForm to
ValueObject.

  1. Have your value object extend ValueObject:

class PersonName < ValueObject
attr_reader :first, :last, :middle
validates_presence_of :first, :last

def initialize(first, middle, last)
@first = first
@middle = middle
@last = last
end
end

Again ValueObject is the renamed version of the ActiveForm class above.
Note that you can now use the standard validation class methods in your
value object. (Perhaps making ValueObject a module, perhaps called
‘Validatable’, would be better?)

  1. Add a validates_component method to the validation class methods as
    follows:

module ActiveRecord::Validations::ClassMethods
def validates_component(*attr_names)
configuration = {}
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)

validates_each(attr_names, configuration) do |record, attr_name, 

value|
dup_value = value.dup
if !dup_value.valid?
dup_value.errors.each { |attr, msg|
record.errors.add(“#{attr_name}_#{attr}”, msg) }
end
end
end
end

You can now use validates_component(:name) in your ActiveRecord objects
to trigger validation of composed_of aggregates. It’s a bit tedious
though to write both the composed_of and validates_component
declarations, so:

  1. Add a composed_of_and_validates method to the validation class
    methods:

module ActiveRecord::Aggregations::ClassMethods
def composed_of_and_validates(part_id, options = {})
composed_of(part_id, options)
validates_component(part_id)
end
end

You can then write an ActiveRecord object that validates its components
like this:

class Person < ActiveRecord::Base
composed_of_and_validates :name,
:class_name => PersonName,
:mapping => [
[ :first_name, :first ],
[ :middle_name, :middle ],
[ :last_name, :last ]
]

end

Simon Harris wrote:

ActiveRecord supports composed_of for value objects which is
fantastic but one thing that it doesn’t seem to support (or at least
I am unable to find any documentation for) validation of the value
objects.

I’m a rails newbie struggling with the same issue. Here’s what I came
up with:

  1. Grab the ActiveForm class described here:
    http://www.realityforge.org/articles/2005/12/02/validations-for-non-activerecord-model-objects

The code is here:

http://www.realityforge.org/files/active_form.rb

I used it pretty much as is, but renamed it from ActiveForm to
ValueObject.

  1. Have your value object extend ValueObject:

class PersonName < ValueObject
attr_reader :first, :last, :middle
validates_presence_of :first, :last

def initialize(first, middle, last)
@first = first
@middle = middle
@last = last
end
end

Again ValueObject is the renamed version of the ActiveForm class above.
Note that you can now use the standard validation class methods in your
value object. (Perhaps making ValueObject a module, perhaps called
‘Validatable’, would be better?)

  1. Add a validates_component method to the validation class methods as
    follows:

module ActiveRecord::Validations::ClassMethods
def validates_component(*attr_names)
configuration = {}
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)

validates_each(attr_names, configuration) do |record, attr_name,

value|
dup_value = value.dup
if !dup_value.valid?
dup_value.errors.each { |attr, msg|
record.errors.add(“#{attr_name}_#{attr}”, msg) }
end
end
end
end

You can now use validates_component(:name) in your ActiveRecord objects
to trigger validation of composed_of aggregates. It’s a bit tedious
though to write both the composed_of and validates_component
declarations, so:

  1. Add a composed_of_and_validates method to the validation class
    methods:

module ActiveRecord::Aggregations::ClassMethods
def composed_of_and_validates(part_id, options = {})
composed_of(part_id, options)
validates_component(part_id)
end
end

You can then write an ActiveRecord object that validates its components
like this:

class Person < ActiveRecord::Base
composed_of_and_validates :name,
:class_name => PersonName,
:mapping => [
[ :first_name, :first ],
[ :middle_name, :middle ],
[ :last_name, :last ]
]

end

Stephen M. wrote:

  dup_value = value.dup
  if !dup_value.valid?
    dup_value.errors.each { |attr, msg| 

record.errors.add("#{attr_name}_#{attr}", msg) }
end
end
end
end

One thing I forgot to mention is the reason for the call to value.dup
above. ActiveRecord freezes value objects once they are attached to an
ActiveRecord object. However validate and valid? change the state of
the object when they add to its errors collection. So without the call
to dup you’d get a “can’t modify frozen object” error.

The weird thing about this approach then is that you can call valid? on
your value object before assigning it to its owner, but not
afterwords:

name = PersonName.new(‘Tom’, ‘T’, ‘Hall’)

this is OK:

name.valid?

Person is an active record object composed_of a name:

person = Person.new
person.name = name

this will blow up:

name.valid? # attempt to modify frozen object
name.dup.valid? # this is OK

Steve