42

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 of puts __method__.)

1
  • 1
    The question relates to Ruby 1.9.2. But these days, if you just found this question, you're probably using Ruby 2+. In Ruby 2+ prepend is a good option. See, e.g., stackoverflow.com/questions/4219277/… Commented Apr 12, 2020 at 19:45

6 Answers 6

72

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" 
Sign up to request clarification or add additional context in comments.

9 Comments

+1 Like it. But Ruby 1.8.7 doesn't support it? NoMethodError: undefined method before' for M:Module`
@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()
Hi, 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.
@reizals See ruby-doc.org/core-2.1.2/UnboundMethod.html#method-i-bind. (Reply is just for everyone's reference.)
Wait, 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)`
|
12

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 

Comments

4

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... 

Comments

3

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

Comments

3

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

1

It is possible with meta-programming.

Another alternative is Aquarium. Aquarium is a framework that implements Aspect-Oriented Programming (AOP) for Ruby. AOP allow you to implement functionality across normal object and method boundaries. Your use case, applying a pre-action on every method, is a basic task of AOP.

2 Comments

I don't know why this was downvoted, either. Perhaps it was because there was no example just a link.
Downvoting for link to random library without any explanation as to why I should click the link

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.