How to decorate a method in Ruby without alias_method_chain
We all know, that if the target class is composed with modules, you can just call super
in a new module. But what if it is an ordinary method in a class?
class Logger
def message(msg)
puts msg
end
end
Say, Logger is a class I can't change (e.g. it is in a gem). And I want Logger to put a "================" line before each message. How do I do that in a beauty way? Inh开发者_运维问答eritance? Aggregation? How?
You could save the original method as an unbound method object instead of saving it in an alias.
Then, you can use define_method
with a block. The block will capture the unbound method_object in a closure, allowing you to use it in your new method, without having pollute your module/class.
The only downside is, you probably can't define a method that yields to or takes a block in this way:
module Mod
unbound_method = instance_method(:original_method)
define_method :original_method do |*args|
#do something before
#call the original method
unbound_method.bind(self).call(*args)
#do something after
end
end
I would either perform subclassing (per @iain's answer) or a custom module inclusion in your own instances:
module LoggerLiner
def message(*args)
puts "="*15
super
end
end
log = Logger.new(STDOUT)
log.extend(LoggerLiner)
This may not be what you mean by "beauty" but probably the simplest way is to have, in your code somewhere:
class Logger
alias message old_message
def message(msg)
puts "================"
old_message(msg)
end
end
You could inherit from logger and use namespacing to use it:
class LinedLogger < Logger
def message(*)
puts "=" * 15
super
end
end
And when you want to use it:
class Post
Logger = LinedLogger
end
Only within the namespace Post
, you will get LinedLogger
instead of Logger
. It's a nice way of limiting your patches. It will not work globally though.
精彩评论