Skip to content

Commit

Permalink
allow the :converter Proc form composed_of to return nil
Browse files Browse the repository at this point in the history
This makes it possible to filter invalid input values before they are passed
into the value-object (like empty strings). This behaviour is only relevant
if the :allow_nil options is set to true. Otherwise you will get
the resulting NoMethodError.
  • Loading branch information
Yves Senn committed May 3, 2012
1 parent 499825e commit fa5f037
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 7 deletions.
15 changes: 8 additions & 7 deletions activerecord/lib/active_record/aggregations.rb
Expand Up @@ -187,7 +187,8 @@ module ClassMethods
# * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
# or a Proc that is called when a new value is assigned to the value object. The converter is
# passed the single value that is used in the assignment and is only called if the new value is
# not an instance of <tt>:class_name</tt>.
# not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
# can return nil to skip the assignment.
#
# Option examples:
# composed_of :temperature, :mapping => %w(reading celsius)
Expand Down Expand Up @@ -234,16 +235,16 @@ def reader_method(name, class_name, mapping, allow_nil, constructor)

def writer_method(name, class_name, mapping, allow_nil, converter)
define_method("#{name}=") do |part|
unless part.is_a?(class_name.constantize) || converter.nil? || part.nil?
part = converter.respond_to?(:call) ?
converter.call(part) :
class_name.constantize.send(converter, part)
end

if part.nil? && allow_nil
mapping.each { |pair| self[pair.first] = nil }
@aggregation_cache[name] = nil
else
unless part.is_a?(class_name.constantize) || converter.nil?
part = converter.respond_to?(:call) ?
converter.call(part) :
class_name.constantize.send(converter, part)
end

mapping.each { |pair| self[pair.first] = part.send(pair.last) }
@aggregation_cache[name] = part.freeze
end
Expand Down
18 changes: 18 additions & 0 deletions activerecord/test/cases/aggregations_test.rb
Expand Up @@ -109,6 +109,24 @@ def test_nil_assignment_results_in_nil
assert_nil customers(:david).gps_location
end

def test_nil_return_from_converter_is_respected_when_allow_nil_is_true
customers(:david).non_blank_gps_location = ""
customers(:david).save
customers(:david).reload
assert_nil customers(:david).non_blank_gps_location
end

def test_nil_return_from_converter_results_in_failure_when_allow_nil_is_false
assert_raises(NoMethodError) do
customers(:barney).gps_location = ""
end
end

def test_do_not_run_the_converter_when_nil_was_set
customers(:david).non_blank_gps_location = nil
assert_nil Customer.gps_conversion_was_run
end

def test_custom_constructor
assert_equal 'Barney GUMBLE', customers(:barney).fullname.to_s
assert_kind_of Fullname, customers(:barney).fullname
Expand Down
5 changes: 5 additions & 0 deletions activerecord/test/models/customer.rb
@@ -1,7 +1,12 @@
class Customer < ActiveRecord::Base

cattr_accessor :gps_conversion_was_run

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), :converter => Proc.new { |balance| balance.to_money }
composed_of :gps_location, :allow_nil => true
composed_of :non_blank_gps_location, :class_name => "GpsLocation", :allow_nil => true, :mapping => %w(gps_location gps_location),
:converter => lambda { |gps| self.gps_conversion_was_run = true; gps.blank? ? nil : GpsLocation.new(gps)}
composed_of :fullname, :mapping => %w(name to_s), :constructor => Proc.new { |name| Fullname.parse(name) }, :converter => :parse
end

Expand Down

0 comments on commit fa5f037

Please sign in to comment.