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__
.)
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"
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
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...
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()
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
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.