Forum: Ruby on Rails Validation with Aggregation

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Simon Harris (Guest)
on 2005-12-16 09:18
(Received via mailing list)
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
Stephen M. (Guest)
on 2005-12-18 22:53
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/va...

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.

2.  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?)

3.   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:

4.   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. (Guest)
on 2005-12-19 00:20
(Received via mailing list)
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/va...

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.

2.  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?)

3.   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:

4.   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. (Guest)
on 2005-12-19 00:20
(Received via mailing list)
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
This topic is locked and can not be replied to.