Class: Module

Inherits:
Object
  • Object
show all
Defined in:
lib/active_storage/patches/delegation.rb

Defined Under Namespace

Classes: DelegationError

Constant Summary collapse

RUBY_RESERVED_KEYWORDS =
%w(__ENCODING__ __LINE__ __FILE__ alias and BEGIN begin break
case class def defined? do else elsif END end ensure false for if in module next nil
not or redo rescue retry return self super then true undef unless until when while yield)
DELEGATION_RESERVED_KEYWORDS =
%w(_ arg args block)
DELEGATION_RESERVED_METHOD_NAMES =
Set.new(
  RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
).freeze

Instance Method Summary collapse

Instance Method Details

#delegate_missing_to(target, allow_nil: nil) ⇒ Object

When building decorators, a common pattern may emerge:

class Partition
  def initialize(event)
    @event = event
  end

  def person
    detail.person || creator
  end

  private
    def respond_to_missing?(name, include_private = false)
      @event.respond_to?(name, include_private)
    end

    def method_missing(method, *args, &block)
      @event.send(method, *args, &block)
    end
end

With Module#delegate_missing_to, the above is condensed to:

class Partition
  delegate_missing_to :@event

  def initialize(event)
    @event = event
  end

  def person
    detail.person || creator
  end
end

The target can be anything callable within the object, e.g. instance variables, methods, constants, etc.

The delegated method must be public on the target, otherwise it will raise DelegationError. If you wish to instead return nil, use the :allow_nil option.

The marshal_dump and _dump methods are exempt from delegation due to possible interference when calling Marshal.dump(object), should the delegation target method of object add or remove instance variables.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/active_storage/patches/delegation.rb', line 64

def delegate_missing_to(target, allow_nil: nil)
  target = target.to_s
  target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)

  module_eval "    def respond_to_missing?(name, include_private = false)\n      # It may look like an oversight, but we deliberately do not pass\n      # +include_private+, because they do not get delegated.\n\n      return false if name == :marshal_dump || name == :_dump\n      \#{target}.respond_to?(name) || super\n    end\n\n    def method_missing(method, *args, &block)\n      if \#{target}.respond_to?(method)\n        \#{target}.public_send(method, *args, &block)\n      else\n        begin\n          super\n        rescue NoMethodError\n          if \#{target}.nil?\n            if \#{allow_nil == true}\n              nil\n            else\n              raise DelegationError, \"\\\#{method} delegated to \#{target}, but \#{target} is nil\"\n            end\n          else\n            raise\n          end\n        end\n      end\n    end\n  RUBY\nend\n", __FILE__, __LINE__ + 1