diff options
| -rw-r--r-- | activesupport/CHANGELOG.md | 47 | ||||
| -rw-r--r-- | activesupport/lib/active_support/core_ext/module/delegation.rb | 61 | ||||
| -rw-r--r-- | activesupport/test/core_ext/module_test.rb | 22 | 
3 files changed, 129 insertions, 1 deletions
| diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 1a169d36be..7f4014cac1 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* +  ## Rails 5.0.0.beta3 (February 24, 2016) ##  *   Deprecate arguments on `assert_nothing_raised`. diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 0d46248582..a97a4add93 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)      options = methods.pop      unless options.is_a?(Hash) && to = options[:to] @@ -215,4 +214,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 0ed66f8c37..ed9cf897a4 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 | 
