Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit 870c7108c86c951b6c9d94fbfc5653c46e39e48b @fredwu committed Sep 11, 2012
@@ -0,0 +1,17 @@
+*.gem
+*.rbc
+.bundle
+.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
@@ -0,0 +1,3 @@
+source 'https://rubygems.org'
+
+gemspec
@@ -0,0 +1,29 @@
+# Ruby Decorators
+
+#### Ruby method decorators inspired by Python.
+
+## Installation
+
+Add this line to your application's Gemfile:
+
+ gem 'ruby_decorators'
+
+And then execute:
+
+ $ bundle
+
+Or install it yourself as:
+
+ $ gem install ruby_decorators
+
+## Usage
+
+```ruby
+ # TODO
+```
+
+## License
+
+Copyright (c) 2012 [Fred Wu](http://fredwu.me/)
+
+Licensed under the [MIT license](http://fredwu.mit-license.org/).
@@ -0,0 +1,12 @@
+require "bundler/gem_tasks"
+
+require 'rake/testtask'
+
+
+Rake::TestTask.new do |t|
+ t.libs.push 'lib'
+ t.test_files = FileList['spec/**/*_spec.rb']
+ t.verbose = true
+end
+
+task :default => :test
@@ -0,0 +1,9 @@
+class RubyDecorator
+ def self.+@
+ RubyDecorators::Stack.decorators << self
+ end
+
+ def +@
+ RubyDecorators::Stack.decorators << self
+ end
+end
@@ -0,0 +1,49 @@
+require "ruby_decorators/version"
+require "ruby_decorator"
+require "ruby_decorators/stack"
+
+module RubyDecorators
+ def method_added(method_name)
+ @decorated_methods ||= []
+
+ return if RubyDecorators::Stack.decorators.empty? ||
+ method_name.to_s =~ /__undecorated_/ ||
+ @decorated_methods.include?(method_name)
+
+ current_decorator = RubyDecorators::Stack.decorators.pop
+ method_visibility = detect_method_visibility(method_name)
+
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
+ alias_method :__undecorated_#{method_name}, :#{method_name}
+
+ @__decorators ||= {}
+ @__decorators["#{method_name}"] = current_decorator
+
+ #{method_visibility}
+ def #{method_name}(*args, &blk)
+ decorator = #{current_decorator}.new
+ decorator ||= self.class.instance_variable_get(:@__decorators)["#{method_name}"]
+
+ if args.any?
+ decorator.call(self.send :__undecorated_#{method_name}, *args, &blk)
+ else
+ decorator.call(self.send :__undecorated_#{method_name}, &blk)
+ end
+ end
+ RUBY_EVAL
+
+ @decorated_methods << method_name
+ end
+
+ private
+
+ def detect_method_visibility(method_name)
+ if private_method_defined?(method_name)
+ :private
+ elsif protected_method_defined?(method_name)
+ :protected
+ else
+ :public
+ end
+ end
+end
Binary file not shown.
@@ -0,0 +1,7 @@
+module RubyDecorators
+ class Stack
+ def self.decorators
+ @decorators ||= []
+ end
+ end
+end
@@ -0,0 +1,3 @@
+module RubyDecorators
+ VERSION = "0.0.1"
+end
@@ -0,0 +1,23 @@
+# -*- encoding: utf-8 -*-
+lib = File.expand_path('../lib', __FILE__)
+$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
+require 'ruby_decorators/version'
+
+Gem::Specification.new do |gem|
+ gem.name = "ruby_decorators"
+ gem.version = RubyDecorators::VERSION
+ gem.authors = ["Fred Wu"]
+ gem.email = ["ifredwu@gmail.com"]
+ gem.description = %q{Ruby method decorators inspired by Python.}
+ gem.summary = gem.description
+ gem.homepage = ""
+
+ gem.files = `git ls-files`.split($/)
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
+ gem.require_paths = ["lib"]
+
+ gem.add_development_dependency 'rake'
+ gem.add_development_dependency 'pry'
+ gem.add_development_dependency 'minitest'
+end
@@ -0,0 +1,123 @@
+require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
+
+describe RubyDecorators do
+ class DummyDecorator < RubyDecorator
+ def call(this)
+ 'I should never be called'
+ end
+ end
+
+ class Batman < RubyDecorator
+ def call(this)
+ this.sub('world', 'batman')
+ end
+ end
+
+ class Catwoman < RubyDecorator
+ def initialize(*args)
+ @args = args.any? ? args : ['catwoman']
+ end
+
+ def call(this)
+ this.sub('world', @args.join(' '))
+ end
+ end
+
+ class DummyClass
+ extend RubyDecorators
+
+ attr_reader :greeting
+
+ def initialize
+ @greeting = 'hello world'
+ end
+
+ def hello_world
+ @greeting
+ end
+
+ +Batman
+ def hello_public
+ @greeting
+ end
+
+ +DummyDecorator
+ def hello_void
+ @greeting
+ end
+
+ +Batman
+ def hello_with_args(arg1, arg2)
+ "#{@greeting} #{arg1} #{arg2}"
+ end
+
+ +Catwoman
+ def hello_catwoman
+ @greeting
+ end
+
+ +Batman
+ def hello_with_block(arg1, arg2, &block)
+ "#{@greeting} #{arg1} #{arg2} #{block.call if block_given?}"
+ end
+
+ +Catwoman.new('super', 'catwoman')
+ def hello_super_catwoman
+ @greeting
+ end
+
+ protected
+
+ +Batman
+ def hello_protected
+ @greeting
+ end
+
+ private
+
+ +Batman
+ def hello_private
+ @greeting
+ end
+ end
+
+ subject { DummyClass.new }
+
+ it "#hello_world" do
+ subject.hello_world.must_equal 'hello world'
+ end
+
+ describe "a simple decorator" do
+ it "decorates a public method" do
+ subject.hello_public.must_equal 'hello batman'
+ end
+
+ it "decorates a protected method" do
+ subject.send(:hello_protected).must_equal 'hello batman'
+ lambda { subject.hello_protected }.must_raise NoMethodError
+ end
+
+ it "decorates a private method" do
+ subject.send(:hello_private).must_equal 'hello batman'
+ lambda { subject.hello_private }.must_raise NoMethodError
+ end
+
+ it "decorates a method with args" do
+ subject.hello_with_args('how are', 'you?').must_equal 'hello batman how are you?'
+ end
+
+ it "decorates a method with a block" do
+ subject.hello_with_block('how are', 'you') { 'man?' }.must_equal 'hello batman how are you man?'
+ end
+ end
+
+ describe "a decorator with args" do
+ it "decorates without any decorator args" do
+ subject.hello_catwoman.must_equal 'hello catwoman'
+ end
+
+ it "decorate a simple method" do
+ subject.hello_super_catwoman.must_equal 'hello super catwoman'
+ end
+ end
+end
@@ -0,0 +1,5 @@
+require 'minitest/autorun'
+require 'minitest/spec'
+require 'pry'
+
+require File.expand_path('../../lib/ruby_decorators', __FILE__)

0 comments on commit 870c710

Please sign in to comment.