Skip to content

Instantly share code, notes, and snippets.

@vjt
Created January 28, 2009 10:23
Show Gist options
  • Save vjt/53886 to your computer and use it in GitHub Desktop.
Save vjt/53886 to your computer and use it in GitHub Desktop.
A simple class decorator.
A simple class decorator. Useful when implementing some sorts of the evil twin pattern.
It works by creating a blank class slate (with Class.new), removing on it EVERY
method, except `__id__`, `__send__` and `initialize`. It then defines the `class`
method to return the original class, and implements both an `initialize` and a
`method_missing`.
The `initialize` sends the `new` method to the original class, therefore creating
a new instance of the original class and saves it in the @_object instance variable.
The `method_missing` in turn forwards any unimplemented method on the decorator to
the instance of the original class saved in the @_object instance variable.
History:
* Originally posted on http://pastie.org/39504 (11 February 2007)
* evolved into http://pastie.org/pastes/39197 (09 February 2007),
* evolved again http://pastie.org/pastes/36010 (27 January 2007).
* Originally used apeiros' blank slate: http://pastie.caboo.se/26558
* Now on gist.github, with finally a comprehensive description and documentation (28 January 2009).
In order to decorate an existing class with new methods you should pass them as blocks
to the #decorate method, defined in Class.
Examples:
class Antani
def antani
'living in Antani..'
end
end
with_cheezeburger = Proc.new do
def cheezeburger!
"CAN I HAZ A CHEEZEBURGER?!"
end
end
B = A.decorate &with_cheezeburger # => B
b = B.new # => #<Antani:0xcafebabe>
# ^- note the invisibility of the decorated class,
# that behaves and looks like the master one.
b.antani # => "living in Antani.."
b.cheezeburger! # => "CAN I HAZ A CHEEZEBURGER?!"
Another example, conceptually identical:
with_address_and_email = Proc.new do
attr_accessor :address, :email
end
class C
def o_rly?
"YA, RLY!"
end
end
D = C.decorate &with_address_and_email # => D
>> d = D.new # => #<C:0xb7a9fa58>
>> d.o_rly? # => "YA, RLY!"
>> d.class # => C
>> d.address = "Antani Street, 23" # => "Antani Street, 23"
>> d.address # => "Antani Street, 23"
Enjoy!
- [email protected]
# Simple class decorator. See the README for more information
# - [email protected]
module Decorator
def self.decorate klass, &block
slate = Class.new
# Inspired by apeiros' blank slate
(slate.instance_methods+slate.private_instance_methods+slate.protected_instance_methods).each { |m|
slate.send :undef_method, m unless %w(__id__ __send__ initialize).include? m
}
slate.class_eval do
define_method(:class) { klass }
def initialize *args
@_object = self.class.new *args
end
def method_missing meth, *args, &block
@_object.send meth, *args, &block
end
end
slate.class_eval &block
slate
end
end
class Class
def decorate &block
Decorator.decorate self, &block
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment