summaryrefslogtreecommitdiff
path: root/lib/delegate.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/delegate.rb')
-rw-r--r--lib/delegate.rb276
1 files changed, 171 insertions, 105 deletions
diff --git a/lib/delegate.rb b/lib/delegate.rb
index e46e4f8c23..0cc3ddb1b0 100644
--- a/lib/delegate.rb
+++ b/lib/delegate.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
# = delegate -- Support for the Delegation Pattern
#
# Documentation by James Edward Gray II and Gavin Sinclair
@@ -17,15 +18,12 @@
# yourself needing this control, have a look at Forwardable which is also in
# the standard library. It may suit your needs better.)
#
-# SimpleDelegator's implementation serves as a nice example if the use of
+# SimpleDelegator's implementation serves as a nice example of the use of
# Delegator:
#
-# class SimpleDelegator < Delegator
-# def initialize(obj)
-# super # pass obj to Delegator constructor, required
-# @delegate_sd_obj = obj # store obj for future use
-# end
+# require 'delegate'
#
+# class SimpleDelegator < Delegator
# def __getobj__
# @delegate_sd_obj # return object we are delegating to, required
# end
@@ -41,9 +39,19 @@
# Be advised, RDoc will not detect delegated methods.
#
class Delegator < BasicObject
+ # The version string
+ VERSION = "0.6.1"
+
kernel = ::Kernel.dup
kernel.class_eval do
- [:to_s,:inspect,:=~,:!~,:===,:<=>,:eql?,:hash].each do |m|
+ alias __raise__ raise
+ [:to_s, :inspect, :!~, :===, :<=>, :hash].each do |m|
+ undef_method m
+ end
+ private_instance_methods.each do |m|
+ if /\Ablock_given\?\z|\Aiterator\?\z|\A__.*__\z/ =~ m
+ next
+ end
undef_method m
end
end
@@ -55,6 +63,12 @@ class Delegator < BasicObject
end
# :startdoc:
+ ##
+ # :method: raise
+ # Use #__raise__ if your Delegator does not have a object to delegate the
+ # #raise method call.
+ #
+
#
# Pass in the _obj_ to delegate method calls to. All methods supported by
# _obj_ will be delegated to.
@@ -64,33 +78,56 @@ class Delegator < BasicObject
end
#
- # Handles the magic of delegation through \_\_getobj\_\_.
+ # Handles the magic of delegation through +__getobj__+.
#
- def method_missing(m, *args, &block)
- target = self.__getobj__
- begin
- target.respond_to?(m) ? target.__send__(m, *args, &block) : super(m, *args, &block)
- ensure
- $@.delete_if {|t| %r"\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:"o =~ t} if $@
+ ruby2_keywords def method_missing(m, *args, &block)
+ r = true
+ target = self.__getobj__ {r = false}
+
+ if r && target_respond_to?(target, m, false)
+ target.__send__(m, *args, &block)
+ elsif ::Kernel.method_defined?(m) || ::Kernel.private_method_defined?(m)
+ ::Kernel.instance_method(m).bind_call(self, *args, &block)
+ else
+ super(m, *args, &block)
end
end
#
# Checks for a method provided by this the delegate object by forwarding the
- # call through \_\_getobj\_\_.
+ # call through +__getobj__+.
#
def respond_to_missing?(m, include_private)
- r = self.__getobj__.respond_to?(m, include_private)
- if r && include_private && !self.__getobj__.respond_to?(m, false)
- warn "#{caller(3)[0]}: delegator does not forward private method \##{m}"
+ r = true
+ target = self.__getobj__ {r = false}
+ r &&= target_respond_to?(target, m, include_private)
+ if r && include_private && !target_respond_to?(target, m, false)
+ warn "delegator does not forward private method \##{m}", uplevel: 3
return false
end
r
end
+ KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?) # :nodoc:
+ private_constant :KERNEL_RESPOND_TO
+
+ # Handle BasicObject instances
+ private def target_respond_to?(target, m, include_private)
+ case target
+ when Object
+ target.respond_to?(m, include_private)
+ else
+ if KERNEL_RESPOND_TO.bind_call(target, :respond_to?)
+ target.respond_to?(m, include_private)
+ else
+ KERNEL_RESPOND_TO.bind_call(target, m, include_private)
+ end
+ end
+ end
+
#
# Returns the methods available to this delegate object as the union
- # of this object's and \_\_getobj\_\_ methods.
+ # of this object's and +__getobj__+ methods.
#
def methods(all=true)
__getobj__.methods(all) | super
@@ -98,7 +135,7 @@ class Delegator < BasicObject
#
# Returns the methods available to this delegate object as the union
- # of this object's and \_\_getobj\_\_ public methods.
+ # of this object's and +__getobj__+ public methods.
#
def public_methods(all=true)
__getobj__.public_methods(all) | super
@@ -106,7 +143,7 @@ class Delegator < BasicObject
#
# Returns the methods available to this delegate object as the union
- # of this object's and \_\_getobj\_\_ protected methods.
+ # of this object's and +__getobj__+ protected methods.
#
def protected_methods(all=true)
__getobj__.protected_methods(all) | super
@@ -131,7 +168,15 @@ class Delegator < BasicObject
end
#
- # Delegates ! to the \_\_getobj\_\_
+ # Returns true if two objects are considered of equal value.
+ #
+ def eql?(obj)
+ return true if obj.equal?(self)
+ obj.eql?(__getobj__)
+ end
+
+ #
+ # Delegates ! to the +__getobj__+
#
def !
!__getobj__
@@ -142,7 +187,7 @@ class Delegator < BasicObject
# method calls are being delegated to.
#
def __getobj__
- raise NotImplementedError, "need to define `__getobj__'"
+ __raise__ ::NotImplementedError, "need to define '__getobj__'"
end
#
@@ -150,17 +195,17 @@ class Delegator < BasicObject
# to _obj_.
#
def __setobj__(obj)
- raise NotImplementedError, "need to define `__setobj__'"
+ __raise__ ::NotImplementedError, "need to define '__setobj__'"
end
#
- # Serialization support for the object returned by \_\_getobj\_\_.
+ # Serialization support for the object returned by +__getobj__+.
#
def marshal_dump
ivars = instance_variables.reject {|var| /\A@delegate_/ =~ var}
[
:__v2__,
- ivars, ivars.map{|var| instance_variable_get(var)},
+ ivars, ivars.map {|var| instance_variable_get(var)},
__getobj__
]
end
@@ -171,15 +216,15 @@ class Delegator < BasicObject
def marshal_load(data)
version, vars, values, obj = data
if version == :__v2__
- vars.each_with_index{|var, i| instance_variable_set(var, values[i])}
+ vars.each_with_index {|var, i| instance_variable_set(var, values[i])}
__setobj__(obj)
else
__setobj__(data)
end
end
- def initialize_clone(obj) # :nodoc:
- self.__setobj__(obj.__getobj__.clone)
+ def initialize_clone(obj, freeze: nil) # :nodoc:
+ self.__setobj__(obj.__getobj__.clone(freeze: freeze))
end
def initialize_dup(obj) # :nodoc:
self.__setobj__(obj.__getobj__.dup)
@@ -187,39 +232,16 @@ class Delegator < BasicObject
private :initialize_clone, :initialize_dup
##
- # :method: trust
- # Trust both the object returned by \_\_getobj\_\_ and self.
- #
-
- ##
- # :method: untrust
- # Untrust both the object returned by \_\_getobj\_\_ and self.
- #
-
- ##
- # :method: taint
- # Taint both the object returned by \_\_getobj\_\_ and self.
- #
-
- ##
- # :method: untaint
- # Untaint both the object returned by \_\_getobj\_\_ and self.
- #
-
- ##
# :method: freeze
- # Freeze both the object returned by \_\_getobj\_\_ and self.
+ # Freeze both the object returned by +__getobj__+ and self.
#
-
- [:trust, :untrust, :taint, :untaint, :freeze].each do |method|
- define_method method do
- __getobj__.send(method)
- super()
- end
+ def freeze
+ __getobj__.freeze
+ super()
end
@delegator_api = self.public_instance_methods
- def self.public_api # :nodoc:
+ def self.public_api # :nodoc:
@delegator_api
end
end
@@ -230,6 +252,36 @@ end
# and even to change the object being delegated to at a later time with
# #__setobj__.
#
+# class User
+# def born_on
+# Date.new(1989, 9, 10)
+# end
+# end
+#
+# require 'delegate'
+#
+# class UserDecorator < SimpleDelegator
+# def birth_year
+# born_on.year
+# end
+# end
+#
+# decorated_user = UserDecorator.new(User.new)
+# decorated_user.birth_year #=> 1989
+# decorated_user.__getobj__ #=> #<User: ...>
+#
+# A SimpleDelegator instance can take advantage of the fact that SimpleDelegator
+# is a subclass of +Delegator+ to call <tt>super</tt> to have methods called on
+# the object being delegated to.
+#
+# class SuperArray < SimpleDelegator
+# def [](*args)
+# super + 1
+# end
+# end
+#
+# SuperArray.new([1])[0] #=> 2
+#
# Here's a simple example that takes advantage of the fact that
# SimpleDelegator's delegation object can be changed at any time.
#
@@ -262,9 +314,13 @@ end
# Non-Nil: 7
# Unique: 6
#
-class SimpleDelegator<Delegator
+class SimpleDelegator < Delegator
# Returns the current object method calls are being delegated to.
def __getobj__
+ unless defined?(@delegate_sd_obj)
+ return yield if block_given?
+ __raise__ ::ArgumentError, "not delegated"
+ end
@delegate_sd_obj
end
@@ -283,7 +339,7 @@ class SimpleDelegator<Delegator
# puts names[1] # => Sinclair
#
def __setobj__(obj)
- raise ArgumentError, "cannot delegate to self" if self.equal?(obj)
+ __raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj)
@delegate_sd_obj = obj
end
end
@@ -291,12 +347,8 @@ end
def Delegator.delegating_block(mid) # :nodoc:
lambda do |*args, &block|
target = self.__getobj__
- begin
- target.__send__(mid, *args, &block)
- ensure
- $@.delete_if {|t| /\A#{Regexp.quote(__FILE__)}:#{__LINE__-2}:/o =~ t} if $@
- end
- end
+ target.__send__(mid, *args, &block)
+ end.ruby2_keywords
end
#
@@ -309,6 +361,14 @@ end
# end
# end
#
+# or:
+#
+# MyClass = DelegateClass(ClassToDelegateTo) do # Step 1
+# def initialize
+# super(obj_of_ClassToDelegateTo) # Step 2
+# end
+# end
+#
# Here's a sample of use from Tempfile which is really a File object with a
# few special rules about storage location and when the File should be
# deleted. That makes for an almost textbook perfect example of how to use
@@ -332,62 +392,68 @@ end
# # ...
# end
#
-def DelegateClass(superclass)
+def DelegateClass(superclass, &block)
klass = Class.new(Delegator)
- methods = superclass.instance_methods
- methods -= ::Delegator.public_api
- methods -= [:to_s,:inspect,:=~,:!~,:===]
+ ignores = [*::Delegator.public_api, :to_s, :inspect, :=~, :!~, :===]
+ protected_instance_methods = superclass.protected_instance_methods
+ protected_instance_methods -= ignores
+ public_instance_methods = superclass.public_instance_methods
+ public_instance_methods -= ignores
+
+ normal, special = public_instance_methods.partition { |m| m.match?(/\A[a-zA-Z]\w*[!\?]?\z/) }
+
+ source = normal.map do |method|
+ "def #{method}(...); __getobj__.#{method}(...); end"
+ end
+
+ protected_instance_methods.each do |method|
+ source << "def #{method}(...); __getobj__.__send__(#{method.inspect}, ...); end"
+ end
+
klass.module_eval do
- def __getobj__ # :nodoc:
+ def __getobj__ # :nodoc:
+ unless defined?(@delegate_dc_obj)
+ return yield if block_given?
+ __raise__ ::ArgumentError, "not delegated"
+ end
@delegate_dc_obj
end
+
def __setobj__(obj) # :nodoc:
- raise ArgumentError, "cannot delegate to self" if self.equal?(obj)
+ __raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj)
@delegate_dc_obj = obj
end
- methods.each do |method|
+
+ class_eval(source.join(";"), __FILE__, __LINE__)
+
+ special.each do |method|
define_method(method, Delegator.delegating_block(method))
end
+
+ protected(*protected_instance_methods)
end
+
klass.define_singleton_method :public_instance_methods do |all=true|
- super(all) - superclass.protected_instance_methods
+ super(all) | superclass.public_instance_methods
end
klass.define_singleton_method :protected_instance_methods do |all=true|
super(all) | superclass.protected_instance_methods
end
- return klass
-end
-
-# :enddoc:
-
-if __FILE__ == $0
- class ExtArray<DelegateClass(Array)
- def initialize()
- super([])
- end
+ klass.define_singleton_method :instance_methods do |all=true|
+ super(all) | superclass.instance_methods
end
-
- ary = ExtArray.new
- p ary.class
- ary.push 25
- p ary
- ary.push 42
- ary.each {|x| p x}
-
- foo = Object.new
- def foo.test
- 25
+ klass.define_singleton_method :public_instance_method do |name|
+ super(name)
+ rescue NameError
+ raise unless self.public_instance_methods.include?(name)
+ superclass.public_instance_method(name)
end
- def foo.iter
- yield self
+ klass.define_singleton_method :instance_method do |name|
+ super(name)
+ rescue NameError
+ raise unless self.instance_methods.include?(name)
+ superclass.instance_method(name)
end
- def foo.error
- raise 'this is OK'
- end
- foo2 = SimpleDelegator.new(foo)
- p foo2
- foo2.instance_eval{print "foo\n"}
- p foo.test == foo2.test # => true
- p foo2.iter{[55,true]} # => true
- foo2.error # raise error!
+ klass.module_eval(&block) if block
+ return klass
end