Created
November 14, 2008 21:44
-
-
Save jcoglan/25104 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Subclass of Module, used for storing methods copied from other | |
# modules and classes. This lets classes shuffle their methods off | |
# into a module to we can place other modules in front of the | |
# class's own methods | |
class StashModule < Module | |
class << self | |
def [](id) | |
@modules[id] | |
end | |
def save(mod) | |
@modules ||= {} | |
@modules[mod.__id__] = mod | |
end | |
end | |
def initialize | |
super | |
@stash = {} | |
self.class.save(self) | |
end | |
def [](name) | |
@stash[name] | |
end | |
def define(source, name) | |
mod_id = __id__ | |
@stash[name] = source.instance_method(name) | |
module_eval <<-DEF | |
def #{name}(*args, &block) | |
m = StashModule[#{mod_id}][#{name.inspect}] | |
m.bind(self).call(*args[0...m.arity], &block) | |
end | |
DEF | |
end | |
end | |
class Module | |
private | |
# Stash all current methods in a module and add that module to | |
# the hierarchy. Permits insertion of new modules "in front" of | |
# the module itself so we can override methods using 'include' | |
def inherit_from_self! | |
stash = StashModule.new | |
instance_methods(false).each do |name| | |
stash.define(self, name) | |
remove_method(name) | |
end | |
include stash | |
end | |
# Includes a module but places it in front of this module's | |
# own methods. The mixin can use 'super' to refer to this module | |
def include_in_front(mixin) | |
inherit_from_self! | |
include mixin | |
end | |
# A more fine-grained approach: overrides a single method and | |
# allows you to refer to the old one using 'super' | |
def override(name, &block) | |
current = StashModule.new | |
current.define(self, name) | |
include current | |
define_method(name, &block) | |
end | |
end | |
# Example: make a class and a module, mix the module into the | |
# class and use 'super' to refer to the class's method from the | |
# mixin. Ordinarily, the class's methods would take precedence | |
# over those from the module | |
class Foo | |
def initialize(name) | |
@name = name | |
end | |
def foo | |
@name.upcase | |
end | |
end | |
puts Foo.new('Mike').foo | |
#=> "MIKE" | |
module Helper | |
def foo(thing) | |
"My name is #{super}! I like #{thing}" | |
end | |
end | |
class Foo | |
include_in_front Helper | |
end | |
puts Foo.new('Mike').foo('Ruby!') | |
#=> "My name is MIKE! I like Ruby!" | |
# Second example, using override | |
class Bar | |
def talk(item) | |
"It's some #{item}" | |
end | |
override :talk do | |
super.upcase | |
end | |
end | |
puts Bar.new.talk('stuff') | |
#=> "IT'S SOME STUFF" | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment