Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
# 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