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
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. 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 -- Posted via http://www.ruby-forum.com/.
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. 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 -- Posted via http://www.ruby-forum.com/.
Stephen Molitor wrote:> 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 > endOne 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 -- Posted via http://www.ruby-forum.com/.