Skip to content

Instantly share code, notes, and snippets.

@ernie
Last active May 31, 2016 10:39
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ernie/7312803 to your computer and use it in GitHub Desktop.
Save ernie/7312803 to your computer and use it in GitHub Desktop.
An alternate take on the delegation class macro provided by ActiveSupport. Updated with Ruby 2.0's caller_locations.
#!/usr/bin/env ruby
class Module
private
def delegate(*args)
dest, prefix = _extract_valid_delegation_options(args.pop)
_define_delegators(caller_locations.first, prefix, dest, args)
end
def delegate_maybe(*args)
dest, prefix = _extract_valid_delegation_options(args.pop)
_define_delegators(
caller_locations.first, prefix,
"(_ = #{dest}; _.nil? ? NilDelegate.new : _)", args
)
end
def _define_delegators(from, prefix, accessor, args)
args.each do |arg|
method_name = [prefix, arg].compact.join('_')
module_eval(
_delegator(accessor, arg, method_name),
from.path, from.lineno - 2
)
end
end
def _delegator(accessor, destination_method, local_method)
%{
def #{local_method}(*args, &block)
#{accessor}.__send__(:#{destination_method}, *args, &block)
end
}
end
def _extract_valid_delegation_options(opts)
if Hash === opts && opts.has_key?(:to)
[opts[:to].to_s.sub(/^([a-z])/, 'self.\1'), opts[:prefix]]
else
raise ArgumentError, 'Invalid delegation options. Delegate with :to.'
end
end
end
class NilDelegate < BasicObject
delegate *(nil.methods - [:__send__, :object_id]), :to => :__nil__
private
def __nil__
nil
end
def method_missing(method_id, *args, &block)
nil
end
def respond_to_missing?(method_id, include_private = false)
true
end
end
class Owner
attr_accessor :name
def initialize(name)
@name = name
end
def play
puts 'ZOMG PLAYTIME!'
end
end
class Cat
attr_accessor :name, :owner
delegate :name, :name=, :to => :owner, :prefix => 'servant'
delegate_maybe :play, :to => :owner
def initialize(name, owner = nil)
@name, @owner = name, owner
end
end
ernie = Owner.new('Ernie')
esther = Cat.new('Esther', ernie)
esther.name # => "Esther"
esther.servant_name # => "Ernie"
esther.play # => "ZOMG PLAYTIME!"
esther.servant_name = 'Ernest'
esther.owner = nil
esther.play # => nil
esther.servant_name
# => undefined method `name' for nil:NilClass (NoMethodError)
@tenderlove
Copy link

diff --git a/delegate.rb b/delegate.rb
index f4194bb..1721a36 100644
--- a/delegate.rb
+++ b/delegate.rb
@@ -3,16 +3,16 @@
 class Module
   private

-  def delegate(*args)
-    dest, prefix = _extract_valid_delegation_options(args.pop)
-    _define_delegators(caller_locations.first, prefix, dest, args)
+  def delegate(*methods, to: nil, prefix: nil)
+    _extract_valid_delegation_options to
+    _define_delegators(caller_locations.first, prefix, to, methods)
   end

-  def delegate_maybe(*args)
-    dest, prefix = _extract_valid_delegation_options(args.pop)
+  def delegate_maybe(*methods, to: nil, prefix: nil)
+    _extract_valid_delegation_options to
     _define_delegators(
       caller_locations.first, prefix,
-      "(_ = #{dest}; _.nil? ? NilDelegate.new : _)", args
+      "(_ = #{to}; _.nil? ? NilDelegate.new : _)", methods
     )
   end

@@ -34,12 +34,8 @@ class Module
     }
   end

-  def _extract_valid_delegation_options(opts)
-    if Hash === opts && opts.has_key?(:to)
-      opts.values_at(:to, :prefix)
-    else
-      raise ArgumentError, 'Invalid delegation options. Delegate with :to.'
-    end
+  def _extract_valid_delegation_options(to)
+    to || raise(ArgumentError, 'Invalid delegation options. Delegate with :to.')
   end
 end

@@ -83,13 +79,21 @@ class Cat
   end
 end

-ernie = Owner.new('Ernie')
-esther = Cat.new('Esther', ernie)
-esther.name # => "Esther"
-esther.servant_name # => "Ernie"
-esther.play # => "ZOMG PLAYTIME!"
-esther.servant_name = 'Ernest'
-esther.owner = nil
-esther.play # => nil
-esther.servant_name
-# => undefined method `name' for nil:NilClass (NoMethodError)
\ No newline at end of file
+require 'minitest/autorun'
+
+describe 'blah' do
+  it "blahblah" do
+    ernie = Owner.new('Ernie')
+    esther = Cat.new('Esther', ernie)
+    esther.name.must_equal "Esther" # => "Esther"
+    esther.servant_name.must_equal "Ernie" # => "Ernie"
+    esther.play # => "ZOMG PLAYTIME!"
+    esther.servant_name = 'Ernest'
+    esther.owner = nil
+    esther.play.must_be_nil # => nil
+    assert_raises NoMethodError do
+    esther.servant_name
+    end
+    # => undefined method `name' for nil:NilClass (NoMethodError)
+  end
+end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment