From f19385c0f0ab976f129d63959999d74f0642ce1c Mon Sep 17 00:00:00 2001 From: Chris Hapgood Date: Fri, 16 May 2008 17:06:57 -0400 Subject: [PATCH] Added support for alternate constuctors for aggregations. The :constructor option to composed_of now allows an alternate constructor to be used when instantiating the "aggregate" value object. The alternate constructor should be a factory method, e.g. :constructor => :parse for converting a UUID string attribute with UUID.parse. --- activerecord/lib/active_record/aggregations.rb | 13 ++++++++----- activerecord/test/cases/aggregations_test.rb | 7 +++++++ activerecord/test/fixtures/customers.yml | 12 +++++++++++- activerecord/test/models/customer.rb | 18 ++++++++++++++++++ 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 61446cd..98edf40 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -130,7 +130,8 @@ module ActiveRecord # * :allow_nil - specifies that the aggregate object will not be instantiated when all mapped # attributes are +nil+. Setting the aggregate class to +nil+ has the effect of writing +nil+ to all mapped attributes. # This defaults to +false+. - # + # * :constructor - a symbol specifying which constructor method will be used to instantiate aggregate + # objects. The default is +new+. # An optional block can be passed to convert the argument that is passed to the writer method into an instance of # :class_name. The block will only be called if the argument is not already an instance of :class_name. # @@ -140,29 +141,31 @@ module ActiveRecord # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ] # composed_of :gps_location # composed_of :gps_location, :allow_nil => true + # composed_of :uuid, :constructor => :parse # def composed_of(part_id, options = {}, &block) - options.assert_valid_keys(:class_name, :mapping, :allow_nil) + options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor) name = part_id.id2name class_name = options[:class_name] || name.camelize mapping = options[:mapping] || [ name, name ] mapping = [ mapping ] unless mapping.first.is_a?(Array) allow_nil = options[:allow_nil] || false + constructor = options[:constructor]|| :new - reader_method(name, class_name, mapping, allow_nil) + reader_method(name, class_name, mapping, allow_nil, constructor) writer_method(name, class_name, mapping, allow_nil, block) create_reflection(:composed_of, part_id, options, self) end private - def reader_method(name, class_name, mapping, allow_nil) + def reader_method(name, class_name, mapping, allow_nil, constructor) module_eval do define_method(name) do |*args| force_reload = args.first || false if (instance_variable_get("@#{name}").nil? || force_reload) && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? }) - instance_variable_set("@#{name}", class_name.constantize.new(*mapping.collect {|pair| read_attribute(pair.first)})) + instance_variable_set("@#{name}", class_name.constantize.send(constructor, *mapping.collect {|pair| read_attribute(pair.first)})) end instance_variable_get("@#{name}") end diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb index 75d1f27..1af0966 100644 --- a/activerecord/test/cases/aggregations_test.rb +++ b/activerecord/test/cases/aggregations_test.rb @@ -107,6 +107,13 @@ class AggregationsTest < ActiveRecord::TestCase customers(:david).gps_location = nil assert_equal nil, customers(:david).gps_location end + + def test_alternate_constructor + assert customers(:chris).fullname + assert customers(:chris).fullname.is_a?(Fullname) + assert_equal 'Chris', customers(:chris).fullname.first + assert_equal 'Hapgood', customers(:chris).fullname.last + end end class OverridingAggregationsTest < ActiveRecord::TestCase diff --git a/activerecord/test/fixtures/customers.yml b/activerecord/test/fixtures/customers.yml index f802aac..9f5f2ce 100644 --- a/activerecord/test/fixtures/customers.yml +++ b/activerecord/test/fixtures/customers.yml @@ -14,4 +14,14 @@ zaphod: address_street: Avenue Road address_city: Hamlet Town address_country: Nation Land - gps_location: NULL \ No newline at end of file + gps_location: NULL + +chris: + id: 3 + name: Chris Hapgood + balance: 0 + address_street: 13 rue du Docteur Le Rouzic + address_city: Le Pouliguen + address_country: United States + gps_location: NULL + diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb index 030bbc6..a1d19ca 100644 --- a/activerecord/test/models/customer.rb +++ b/activerecord/test/models/customer.rb @@ -2,6 +2,7 @@ class Customer < ActiveRecord::Base composed_of :address, :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ], :allow_nil => true composed_of(:balance, :class_name => "Money", :mapping => %w(balance amount)) { |balance| balance.to_money } composed_of :gps_location, :allow_nil => true + composed_of :fullname, :mapping => %w(name to_s), :constructor => :parse end class Address @@ -53,3 +54,20 @@ class GpsLocation self.latitude == other.latitude && self.longitude == other.longitude end end + +class Fullname + attr_reader :first, :last + + def self.parse(str) + return nil unless str + new(*str.to_s.split) + end + + def initialize(first, last = nil) + @first, @last = first, last + end + + def to_s + "#{first} #{last}" + end +end \ No newline at end of file -- 1.5.5.1