PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/delegate.rb

https://github.com/wanabe/ruby
Ruby | 444 lines | 195 code | 28 blank | 221 comment | 20 complexity | 6740c74714b4e0aa3327e1b3e5836565 MD5 | raw file
Possible License(s): LGPL-2.1, AGPL-3.0, 0BSD, Unlicense, GPL-2.0, BSD-3-Clause
  1. # frozen_string_literal: true
  2. # = delegate -- Support for the Delegation Pattern
  3. #
  4. # Documentation by James Edward Gray II and Gavin Sinclair
  5. ##
  6. # This library provides three different ways to delegate method calls to an
  7. # object. The easiest to use is SimpleDelegator. Pass an object to the
  8. # constructor and all methods supported by the object will be delegated. This
  9. # object can be changed later.
  10. #
  11. # Going a step further, the top level DelegateClass method allows you to easily
  12. # setup delegation through class inheritance. This is considerably more
  13. # flexible and thus probably the most common use for this library.
  14. #
  15. # Finally, if you need full control over the delegation scheme, you can inherit
  16. # from the abstract class Delegator and customize as needed. (If you find
  17. # yourself needing this control, have a look at Forwardable which is also in
  18. # the standard library. It may suit your needs better.)
  19. #
  20. # SimpleDelegator's implementation serves as a nice example of the use of
  21. # Delegator:
  22. #
  23. # require 'delegate'
  24. #
  25. # class SimpleDelegator < Delegator
  26. # def __getobj__
  27. # @delegate_sd_obj # return object we are delegating to, required
  28. # end
  29. #
  30. # def __setobj__(obj)
  31. # @delegate_sd_obj = obj # change delegation object,
  32. # # a feature we're providing
  33. # end
  34. # end
  35. #
  36. # == Notes
  37. #
  38. # Be advised, RDoc will not detect delegated methods.
  39. #
  40. class Delegator < BasicObject
  41. VERSION = "0.2.0"
  42. kernel = ::Kernel.dup
  43. kernel.class_eval do
  44. alias __raise__ raise
  45. [:to_s, :inspect, :=~, :!~, :===, :<=>, :hash].each do |m|
  46. undef_method m
  47. end
  48. private_instance_methods.each do |m|
  49. if /\Ablock_given\?\z|\Aiterator\?\z|\A__.*__\z/ =~ m
  50. next
  51. end
  52. undef_method m
  53. end
  54. end
  55. include kernel
  56. # :stopdoc:
  57. def self.const_missing(n)
  58. ::Object.const_get(n)
  59. end
  60. # :startdoc:
  61. ##
  62. # :method: raise
  63. # Use #__raise__ if your Delegator does not have a object to delegate the
  64. # #raise method call.
  65. #
  66. #
  67. # Pass in the _obj_ to delegate method calls to. All methods supported by
  68. # _obj_ will be delegated to.
  69. #
  70. def initialize(obj)
  71. __setobj__(obj)
  72. end
  73. #
  74. # Handles the magic of delegation through \_\_getobj\_\_.
  75. #
  76. ruby2_keywords def method_missing(m, *args, &block)
  77. r = true
  78. target = self.__getobj__ {r = false}
  79. if r && target_respond_to?(target, m, false)
  80. target.__send__(m, *args, &block)
  81. elsif ::Kernel.method_defined?(m) || ::Kernel.private_method_defined?(m)
  82. ::Kernel.instance_method(m).bind_call(self, *args, &block)
  83. else
  84. super(m, *args, &block)
  85. end
  86. end
  87. #
  88. # Checks for a method provided by this the delegate object by forwarding the
  89. # call through \_\_getobj\_\_.
  90. #
  91. def respond_to_missing?(m, include_private)
  92. r = true
  93. target = self.__getobj__ {r = false}
  94. r &&= target_respond_to?(target, m, include_private)
  95. if r && include_private && !target_respond_to?(target, m, false)
  96. warn "delegator does not forward private method \##{m}", uplevel: 3
  97. return false
  98. end
  99. r
  100. end
  101. KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?)
  102. private_constant :KERNEL_RESPOND_TO
  103. # Handle BasicObject instances
  104. private def target_respond_to?(target, m, include_private)
  105. case target
  106. when Object
  107. target.respond_to?(m, include_private)
  108. else
  109. if KERNEL_RESPOND_TO.bind_call(target, :respond_to?)
  110. target.respond_to?(m, include_private)
  111. else
  112. KERNEL_RESPOND_TO.bind_call(target, m, include_private)
  113. end
  114. end
  115. end
  116. #
  117. # Returns the methods available to this delegate object as the union
  118. # of this object's and \_\_getobj\_\_ methods.
  119. #
  120. def methods(all=true)
  121. __getobj__.methods(all) | super
  122. end
  123. #
  124. # Returns the methods available to this delegate object as the union
  125. # of this object's and \_\_getobj\_\_ public methods.
  126. #
  127. def public_methods(all=true)
  128. __getobj__.public_methods(all) | super
  129. end
  130. #
  131. # Returns the methods available to this delegate object as the union
  132. # of this object's and \_\_getobj\_\_ protected methods.
  133. #
  134. def protected_methods(all=true)
  135. __getobj__.protected_methods(all) | super
  136. end
  137. # Note: no need to specialize private_methods, since they are not forwarded
  138. #
  139. # Returns true if two objects are considered of equal value.
  140. #
  141. def ==(obj)
  142. return true if obj.equal?(self)
  143. self.__getobj__ == obj
  144. end
  145. #
  146. # Returns true if two objects are not considered of equal value.
  147. #
  148. def !=(obj)
  149. return false if obj.equal?(self)
  150. __getobj__ != obj
  151. end
  152. #
  153. # Returns true if two objects are considered of equal value.
  154. #
  155. def eql?(obj)
  156. return true if obj.equal?(self)
  157. obj.eql?(__getobj__)
  158. end
  159. #
  160. # Delegates ! to the \_\_getobj\_\_
  161. #
  162. def !
  163. !__getobj__
  164. end
  165. #
  166. # This method must be overridden by subclasses and should return the object
  167. # method calls are being delegated to.
  168. #
  169. def __getobj__
  170. __raise__ ::NotImplementedError, "need to define `__getobj__'"
  171. end
  172. #
  173. # This method must be overridden by subclasses and change the object delegate
  174. # to _obj_.
  175. #
  176. def __setobj__(obj)
  177. __raise__ ::NotImplementedError, "need to define `__setobj__'"
  178. end
  179. #
  180. # Serialization support for the object returned by \_\_getobj\_\_.
  181. #
  182. def marshal_dump
  183. ivars = instance_variables.reject {|var| /\A@delegate_/ =~ var}
  184. [
  185. :__v2__,
  186. ivars, ivars.map {|var| instance_variable_get(var)},
  187. __getobj__
  188. ]
  189. end
  190. #
  191. # Reinitializes delegation from a serialized object.
  192. #
  193. def marshal_load(data)
  194. version, vars, values, obj = data
  195. if version == :__v2__
  196. vars.each_with_index {|var, i| instance_variable_set(var, values[i])}
  197. __setobj__(obj)
  198. else
  199. __setobj__(data)
  200. end
  201. end
  202. def initialize_clone(obj, freeze: nil) # :nodoc:
  203. self.__setobj__(obj.__getobj__.clone(freeze: freeze))
  204. end
  205. def initialize_dup(obj) # :nodoc:
  206. self.__setobj__(obj.__getobj__.dup)
  207. end
  208. private :initialize_clone, :initialize_dup
  209. ##
  210. # :method: freeze
  211. # Freeze both the object returned by \_\_getobj\_\_ and self.
  212. #
  213. def freeze
  214. __getobj__.freeze
  215. super()
  216. end
  217. @delegator_api = self.public_instance_methods
  218. def self.public_api # :nodoc:
  219. @delegator_api
  220. end
  221. end
  222. ##
  223. # A concrete implementation of Delegator, this class provides the means to
  224. # delegate all supported method calls to the object passed into the constructor
  225. # and even to change the object being delegated to at a later time with
  226. # #__setobj__.
  227. #
  228. # class User
  229. # def born_on
  230. # Date.new(1989, 9, 10)
  231. # end
  232. # end
  233. #
  234. # require 'delegate'
  235. #
  236. # class UserDecorator < SimpleDelegator
  237. # def birth_year
  238. # born_on.year
  239. # end
  240. # end
  241. #
  242. # decorated_user = UserDecorator.new(User.new)
  243. # decorated_user.birth_year #=> 1989
  244. # decorated_user.__getobj__ #=> #<User: ...>
  245. #
  246. # A SimpleDelegator instance can take advantage of the fact that SimpleDelegator
  247. # is a subclass of +Delegator+ to call <tt>super</tt> to have methods called on
  248. # the object being delegated to.
  249. #
  250. # class SuperArray < SimpleDelegator
  251. # def [](*args)
  252. # super + 1
  253. # end
  254. # end
  255. #
  256. # SuperArray.new([1])[0] #=> 2
  257. #
  258. # Here's a simple example that takes advantage of the fact that
  259. # SimpleDelegator's delegation object can be changed at any time.
  260. #
  261. # class Stats
  262. # def initialize
  263. # @source = SimpleDelegator.new([])
  264. # end
  265. #
  266. # def stats(records)
  267. # @source.__setobj__(records)
  268. #
  269. # "Elements: #{@source.size}\n" +
  270. # " Non-Nil: #{@source.compact.size}\n" +
  271. # " Unique: #{@source.uniq.size}\n"
  272. # end
  273. # end
  274. #
  275. # s = Stats.new
  276. # puts s.stats(%w{James Edward Gray II})
  277. # puts
  278. # puts s.stats([1, 2, 3, nil, 4, 5, 1, 2])
  279. #
  280. # Prints:
  281. #
  282. # Elements: 4
  283. # Non-Nil: 4
  284. # Unique: 4
  285. #
  286. # Elements: 8
  287. # Non-Nil: 7
  288. # Unique: 6
  289. #
  290. class SimpleDelegator < Delegator
  291. # Returns the current object method calls are being delegated to.
  292. def __getobj__
  293. unless defined?(@delegate_sd_obj)
  294. return yield if block_given?
  295. __raise__ ::ArgumentError, "not delegated"
  296. end
  297. @delegate_sd_obj
  298. end
  299. #
  300. # Changes the delegate object to _obj_.
  301. #
  302. # It's important to note that this does *not* cause SimpleDelegator's methods
  303. # to change. Because of this, you probably only want to change delegation
  304. # to objects of the same type as the original delegate.
  305. #
  306. # Here's an example of changing the delegation object.
  307. #
  308. # names = SimpleDelegator.new(%w{James Edward Gray II})
  309. # puts names[1] # => Edward
  310. # names.__setobj__(%w{Gavin Sinclair})
  311. # puts names[1] # => Sinclair
  312. #
  313. def __setobj__(obj)
  314. __raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj)
  315. @delegate_sd_obj = obj
  316. end
  317. end
  318. def Delegator.delegating_block(mid) # :nodoc:
  319. lambda do |*args, &block|
  320. target = self.__getobj__
  321. target.__send__(mid, *args, &block)
  322. end.ruby2_keywords
  323. end
  324. #
  325. # The primary interface to this library. Use to setup delegation when defining
  326. # your class.
  327. #
  328. # class MyClass < DelegateClass(ClassToDelegateTo) # Step 1
  329. # def initialize
  330. # super(obj_of_ClassToDelegateTo) # Step 2
  331. # end
  332. # end
  333. #
  334. # or:
  335. #
  336. # MyClass = DelegateClass(ClassToDelegateTo) do # Step 1
  337. # def initialize
  338. # super(obj_of_ClassToDelegateTo) # Step 2
  339. # end
  340. # end
  341. #
  342. # Here's a sample of use from Tempfile which is really a File object with a
  343. # few special rules about storage location and when the File should be
  344. # deleted. That makes for an almost textbook perfect example of how to use
  345. # delegation.
  346. #
  347. # class Tempfile < DelegateClass(File)
  348. # # constant and class member data initialization...
  349. #
  350. # def initialize(basename, tmpdir=Dir::tmpdir)
  351. # # build up file path/name in var tmpname...
  352. #
  353. # @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
  354. #
  355. # # ...
  356. #
  357. # super(@tmpfile)
  358. #
  359. # # below this point, all methods of File are supported...
  360. # end
  361. #
  362. # # ...
  363. # end
  364. #
  365. def DelegateClass(superclass, &block)
  366. klass = Class.new(Delegator)
  367. ignores = [*::Delegator.public_api, :to_s, :inspect, :=~, :!~, :===]
  368. protected_instance_methods = superclass.protected_instance_methods
  369. protected_instance_methods -= ignores
  370. public_instance_methods = superclass.public_instance_methods
  371. public_instance_methods -= ignores
  372. klass.module_eval do
  373. def __getobj__ # :nodoc:
  374. unless defined?(@delegate_dc_obj)
  375. return yield if block_given?
  376. __raise__ ::ArgumentError, "not delegated"
  377. end
  378. @delegate_dc_obj
  379. end
  380. def __setobj__(obj) # :nodoc:
  381. __raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj)
  382. @delegate_dc_obj = obj
  383. end
  384. protected_instance_methods.each do |method|
  385. define_method(method, Delegator.delegating_block(method))
  386. protected method
  387. end
  388. public_instance_methods.each do |method|
  389. define_method(method, Delegator.delegating_block(method))
  390. end
  391. end
  392. klass.define_singleton_method :public_instance_methods do |all=true|
  393. super(all) | superclass.public_instance_methods
  394. end
  395. klass.define_singleton_method :protected_instance_methods do |all=true|
  396. super(all) | superclass.protected_instance_methods
  397. end
  398. klass.define_singleton_method :instance_methods do |all=true|
  399. super(all) | superclass.instance_methods
  400. end
  401. klass.define_singleton_method :public_instance_method do |name|
  402. super(name)
  403. rescue NameError
  404. raise unless self.public_instance_methods.include?(name)
  405. superclass.public_instance_method(name)
  406. end
  407. klass.define_singleton_method :instance_method do |name|
  408. super(name)
  409. rescue NameError
  410. raise unless self.instance_methods.include?(name)
  411. superclass.instance_method(name)
  412. end
  413. klass.module_eval(&block) if block
  414. return klass
  415. end