Executing code for every method call in a Ruby module
Solution 1
Like this:
module M
def self.before(*names)
names.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
yield
m.bind(self).(*args, &block)
end
end
end
end
module M
def hello
puts "yo"
end
def bye
puts "bum"
end
before(*instance_methods) { puts "start" }
end
class C
include M
end
C.new.bye #=> "start" "bum"
C.new.hello #=> "start" "yo"
Solution 2
This is exactly what aspector is created for.
With aspector you don't need to write the boilerplate metaprogramming code. You can even go one step further to extract the common logic into a separate aspect class and test it independently.
require 'aspector'
module MyModule
aspector do
before :go_forth, :add_multiply do
...
end
end
def go_forth
# code particular to this method follows ...
end
def and_multiply
# then something completely different ...
end
end
Solution 3
You can implement it with method_missing
through proxy Module, like this:
module MyModule
module MyRealModule
def self.go_forth
puts "it works!"
# code particular to this method follows ...
end
def self.and_multiply
puts "it works!"
# then something completely different ...
end
end
def self.method_missing(m, *args, &block)
reused_statement
if MyModule::MyRealModule.methods.include?( m.to_s )
MyModule::MyRealModule.send(m)
else
super
end
end
def self.reused_statement
puts "reused statement"
end
end
MyModule.go_forth
#=> it works!
MyModule.stop_forth
#=> NoMethodError...
Solution 4
You can do this by metaprogramming technique, here's an example:
module YourModule
def included(mod)
def mod.method_added(name)
return if @added
@added = true
original_method = "original #{name}"
alias_method original_method, name
define_method(name) do |*args|
reused_statement
result = send original_method, *args
puts "The method #{name} called!"
result
end
@added = false
end
end
def reused_statement
end
end
module MyModule
include YourModule
def go_forth
end
def and_multiply
end
end
works only in ruby 1.9 and higher
UPDATE: and also can't use block, i.e. no yield in instance methods
Solution 5
I dunno, why I was downvoted - but a proper AOP framework is better than meta-programming hackery. And thats what OP was trying to achieve.
http://debasishg.blogspot.com/2006/06/does-ruby-need-aop.html
Another Solution could be:
module Aop
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def before_filter(method_name, options = {})
aop_methods = Array(options[:only]).compact
return if aop_methods.empty?
aop_methods.each do |m|
alias_method "#{m}_old", m
class_eval <<-RUBY,__FILE__,__LINE__ + 1
def #{m}
#{method_name}
#{m}_old
end
RUBY
end
end
end
end
module Bar
def hello
puts "Running hello world"
end
end
class Foo
include Bar
def find_hello
puts "Running find hello"
end
include Aop
before_filter :find_hello, :only => :hello
end
a = Foo.new()
a.hello()
Comments
-
GladstoneKeep almost 2 years
I'm writing a module in Ruby 1.9.2 that defines several methods. When any of these methods is called, I want each of them to execute a certain statement first.
module MyModule def go_forth a re-used statement # code particular to this method follows ... end def and_multiply a re-used statement # then something completely different ... end end
But I want to avoid putting that
a re-used statement
code explicitly in every single method. Is there a way to do so?(If it matters,
a re-used statement
will have each method, when called, print its own name. It will do so via some variant ofputs __method__
.) -
fl00r about 13 years+1 Like it. But Ruby 1.8.7 doesn't support it?
NoMethodError: undefined method
before' for M:Module` -
horseyguy about 13 years@fl00r, all you should have to change to have it work in 1.8.7 is the proc invocation syntax, i'm using
.()
(which is 1.9 only) rather than.call()
-
nurettin over 11 yearsI don't know why this was downvoted, either. Perhaps it was because there was no example just a link.
-
reizals almost 10 yearsHi, could you explain me what exactly m.bind(self).(*args, &block) do? I've search the ruby documentation and many pages from google, but I still don't know how it works. Many thx for help.
-
konsolebox almost 10 years@reizals See ruby-doc.org/core-2.1.2/UnboundMethod.html#method-i-bind. (Reply is just for everyone's reference.)
-
hachpai over 9 yearsSo, the location of the bind is important? We can't make it at the beggining of the class definition?
-
DeejUK almost 9 yearsDownvoting for link to random library without any explanation as to why I should click the link
-
Michael K Madison over 6 yearsDude, this is beautiful. Thanks for sharing this!
-
Michael K Madison over 6 years@horseyguy Wait, are we defining an overriding method? So
before
needs to be defined after the original method? -
Michael K Madison over 6 yearsI didn't even know about .(), nice.
-
Michael K Madison about 6 yearsWait, why doesn't this work if you use the before method in the C class definition? if you move
before(*instance_methods) { puts "start " }
to C class I get<class:C>': undefined method
before' for C:Class (NoMethodError)` -
Rasna Shakya about 2 yearsHow to pass arguments onto it?