aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md47
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb61
-rw-r--r--activesupport/test/core_ext/module_test.rb22
3 files changed, 129 insertions, 1 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 25b8af7d34..812e18a253 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,50 @@
+* Introduce Module#delegate_missing_to.
+
+ When building a decorator, a common pattern emerges:
+
+ class Partition
+ def initialize(first_event)
+ @events = [ first_event ]
+ end
+
+ def people
+ if @events.first.detail.people.any?
+ @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ else
+ @events.collect(&:creator).uniq
+ end
+ end
+
+ private
+ def respond_to_missing?(name, include_private = false)
+ @events.respond_to?(name, include_private)
+ end
+
+ def method_missing(method, *args, &block)
+ @events.send(method, *args, &block)
+ end
+ end
+
+ With `Module#delegate_missing_to`, the above is condensed to:
+
+ class Partition
+ delegate_missing_to :@events
+
+ def initialize(first_event)
+ @events = [ first_event ]
+ end
+
+ def people
+ if @events.first.detail.people.any?
+ @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ else
+ @events.collect(&:creator).uniq
+ end
+ end
+ end
+
+ *Genadi Samokovarov*, *DHH*
+
* Rescuable: If a handler doesn't match the exception, check for handlers
matching the exception's cause.
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 24450cd221..fe680c87b8 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -148,7 +148,6 @@ class Module
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
#
# The target method must be public, otherwise it will raise +NoMethodError+.
- #
def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
unless to
raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
@@ -212,4 +211,64 @@ class Module
module_eval(method_def, file, line)
end
end
+
+ # When building decorators, a common pattern may emerge:
+ #
+ # class Partition
+ # def initialize(first_event)
+ # @events = [ first_event ]
+ # end
+ #
+ # def people
+ # if @events.first.detail.people.any?
+ # @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ # else
+ # @events.collect(&:creator).uniq
+ # end
+ # end
+ #
+ # private
+ # def respond_to_missing?(name, include_private = false)
+ # @events.respond_to?(name, include_private)
+ # end
+ #
+ # def method_missing(method, *args, &block)
+ # @events.send(method, *args, &block)
+ # end
+ # end
+ #
+ # With `Module#delegate_missing_to`, the above is condensed to:
+ #
+ # class Partition
+ # delegate_missing_to :@events
+ #
+ # def initialize(first_event)
+ # @events = [ first_event ]
+ # end
+ #
+ # def people
+ # if @events.first.detail.people.any?
+ # @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ # else
+ # @events.collect(&:creator).uniq
+ # end
+ # end
+ # end
+ #
+ # The target can be anything callable withing the object. E.g. instance
+ # variables, methods, constants ant the likes.
+ def delegate_missing_to(target)
+ target = target.to_s
+ target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
+
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def respond_to_missing?(name, include_private = false)
+ #{target}.respond_to?(name, include_private)
+ end
+
+ def method_missing(method, *args, &block)
+ #{target}.send(method, *args, &block)
+ end
+ RUBY
+ end
end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index ae4aed0554..0120aae492 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -83,6 +83,20 @@ Product = Struct.new(:name) do
end
end
+DecoratedTester = Struct.new(:client) do
+ delegate_missing_to :client
+end
+
+class DecoratedReserved
+ delegate_missing_to :case
+
+ attr_reader :case
+
+ def initialize(kase)
+ @case = kase
+ end
+end
+
class Block
def hello?
true
@@ -316,6 +330,14 @@ class ModuleTest < ActiveSupport::TestCase
assert has_block.hello?
end
+ def test_delegate_to_missing_with_method
+ assert_equal "David", DecoratedTester.new(@david).name
+ end
+
+ def test_delegate_to_missing_with_reserved_methods
+ assert_equal "David", DecoratedReserved.new(@david).name
+ end
+
def test_parent
assert_equal Yz::Zy, Yz::Zy::Cd.parent
assert_equal Yz, Yz::Zy.parent