web-dev-qa-db-fra.com

Exécuter du code pour chaque appel de méthode dans un module Ruby

J'écris un module dans Ruby 1.9.2 qui définit plusieurs méthodes. Lorsque l'une de ces méthodes est appelée, je souhaite que chacune d'entre elles exécute d'abord une certaine instruction.

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

Mais je veux éviter de mettre ce code a re-used statement explicitement dans chaque méthode. Y a-t-il un moyen de le faire?

(Si cela compte, a re-used statement demandera à chaque méthode, lorsqu'elle est appelée, d'afficher son propre nom. Elle le fera via une variante de puts __method__.)

36
GladstoneKeep

Comme ça:

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"
64
horseyguy

C'est exactement ce pour quoiaspectorest créé.

Avec Aspector, vous n'avez pas besoin d'écrire le code de métaprogrammation standard. Vous pouvez même aller plus loin pour extraire la logique commune dans une classe d'aspect séparée et la tester indépendamment.

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
11
Guoliang Cao

Vous pouvez l'implémenter avec method_missing via un module proxy, comme ceci:

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

Je ne sais pas, pourquoi mon vote a été négatif - mais un cadre AOP approprié est préférable à un programme de méta-programmation. Et c’est ce que l’OP essayait de réaliser.

http://debasishg.blogspot.com/2006/06/does-Ruby-need-aop.html

Une autre solution pourrait être:

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()
3
Hemant Kumar

Vous pouvez le faire par la technique de métaprogrammation, voici un exemple: 

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

ne fonctionne que dans Ruby 1.9 et supérieur

UPDATE: et ne peut pas non plus utiliser block, c’est-à-dire aucun rendement dans les méthodes d’instance

2
megas

C'est possible avec la méta-programmation. 

Une autre alternative est Aquarium . Aquarium est un framework qui implémente la programmation orientée aspect (AOP) pour Ruby. Les AOP vous permettent d'implémenter des fonctionnalités sur les limites normales des objets et des méthodes. Votre cas d'utilisation, appliquer une pré-action sur chaque méthode, est une tâche de base de l'AOP.

0
Hemant Kumar