Skip to content

Commit

Permalink
Support multiple decorators, cleaned up some code
Browse files Browse the repository at this point in the history
  • Loading branch information
fredwu committed Sep 12, 2012
1 parent f5db096 commit f4b241b
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 33 deletions.
4 changes: 2 additions & 2 deletions lib/ruby_decorator.rb
@@ -1,9 +1,9 @@
class RubyDecorator
def self.+@
RubyDecorators::Stack.decorators << self
RubyDecorators::Stack.all << self
end

def +@
RubyDecorators::Stack.decorators << self
RubyDecorators::Stack.all << self
end
end
46 changes: 22 additions & 24 deletions lib/ruby_decorators.rb
@@ -1,43 +1,41 @@
require "ruby_decorators/version"
require "ruby_decorator"
require "ruby_decorators/stack"

module RubyDecorators
def method_added(method_name)
@__decorated_methods ||= []
class Stack
def self.all
@all ||= []
end
end

return if RubyDecorators::Stack.decorators.empty? ||
method_name.to_s =~ /__undecorated_/ ||
@__decorated_methods.include?(method_name)
def method_added(method_name)
super

current_decorator = RubyDecorators::Stack.decorators.pop
method_visibility = detect_method_visibility(method_name)
@methods ||= {}
@decorators ||= {}

class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
alias_method :__undecorated_#{method_name}, :#{method_name}
return if RubyDecorators::Stack.all.empty?

@__decorators ||= {}
@__decorators["#{method_name}"] = current_decorator
@methods[method_name] = instance_method(method_name)
@decorators[method_name] = RubyDecorators::Stack.all.pop(42)

#{method_visibility}
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
#{method_visibility_for(method_name)}
def #{method_name}(*args, &blk)
decorator = #{current_decorator}.new
decorator ||= self.class.instance_variable_get(:@__decorators)["#{method_name}"]
if args.any?
decorator.call(method(:__undecorated_#{method_name}), *args, &blk)
else
decorator.call(method(:__undecorated_#{method_name}), &blk)
end
decorators = self.class.instance_variable_get(:@decorators)[:#{method_name}]
method = self.class.instance_variable_get(:@methods)[:#{method_name}]
decorators.inject(method.bind(self)) do |method, decorator|
decorator = decorator.new if decorator.respond_to?(:new)
lambda { |*a, &b| decorator.call(method, *a, &b) }
end.call(*args, &blk)
end
RUBY_EVAL

@__decorated_methods << method_name
end

private

def detect_method_visibility(method_name)
def method_visibility_for(method_name)
if private_method_defined?(method_name)
:private
elsif protected_method_defined?(method_name)
Expand Down
7 changes: 0 additions & 7 deletions lib/ruby_decorators/stack.rb

This file was deleted.

38 changes: 38 additions & 0 deletions spec/ruby_decorators_spec.rb
Expand Up @@ -7,6 +7,12 @@ def call(this)
end
end

class Hi < RubyDecorator
def call(this, *args, &blk)
this.call(*args, &blk).sub('hello', 'hi')
end
end

class Batman < RubyDecorator
def call(this, *args, &blk)
this.call(*args, &blk).sub('world', 'batman')
Expand All @@ -23,6 +29,15 @@ def call(this, *args, &blk)
end
end

class DummyClass2
extend RubyDecorators

+Hi
def hi
'hello'
end
end

class DummyClass
extend RubyDecorators

Expand Down Expand Up @@ -68,6 +83,18 @@ def hello_super_catwoman
@greeting
end

+Hi
+Batman
def hi_batman
@greeting
end

+Hi
+Catwoman.new('super', 'catwoman')
def hi_super_catwoman(arg1)
"#{@greeting}#{arg1}"
end

protected

+Batman
Expand All @@ -86,6 +113,7 @@ def hello_private
subject { DummyClass.new }

it "#hello_world" do
DummyClass2.new.hi.must_equal 'hi'
subject.hello_world.must_equal 'hello world'
end

Expand Down Expand Up @@ -126,4 +154,14 @@ def hello_private
subject.hello_super_catwoman.must_equal 'hello super catwoman'
end
end

describe "multiple decorators" do
it "decorates a simple method" do
subject.hi_batman.must_equal 'hi batman'
end

it "decorates a method with args" do
subject.hi_super_catwoman('!').must_equal 'hi super catwoman!'
end
end
end

0 comments on commit f4b241b

Please sign in to comment.