Выполнение кода для каждого вызова метода в модуле Ruby
Я пишу модуль в Ruby 1.9.2, который определяет несколько методов. Когда вызывается какой-либо из этих методов, я хочу, чтобы каждый из них выполнял определенный оператор в первую очередь.
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
Но я хочу, чтобы я не скрывал этот код a re-used statement
в каждом отдельном методе. Есть ли способ сделать это?
(Если это имеет значение, a re-used statement
будет иметь каждый метод, когда он вызывается, напечатать собственное имя. Он сделает это через какой-то вариант puts __method__
.)
Ответы
Ответ 1
Вот так:
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"
Ответ 2
Это именно то, что создано aspector.
С аспектром вам не нужно писать код метапрограммирования шаблона. Вы даже можете сделать еще один шаг, чтобы извлечь общую логику в отдельный класс аспект и проверить ее самостоятельно.
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
Ответ 3
Вы можете реализовать его с помощью method_missing
через прокси-модуль, например:
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
Я не знаю, почему я был заблокирован, но правильная структура AOP лучше, чем мета-программирование хакеров. И это то, чего пытался добиться OP.
http://debasishg.blogspot.com/2006/06/does-ruby-need-aop.html
Другим решением может быть:
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()
Ответ 5
Вы можете сделать это методом метапрограммирования, вот пример:
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
работает только в рубине 1.9 и выше
UPDATE: а также не может использовать блок, т.е. нет выходных данных в методах экземпляра
Ответ 6
Это возможно при метапрограммировании.
Другой альтернативой является Aquarium. Аквариум - это основа, которая реализует аспектно-ориентированное программирование (АОП) для Ruby. AOP позволяет реализовать функциональные возможности в рамках обычных границ объектов и методов. Ваш прецедент, применяя предварительное действие для каждого метода, является основной задачей АОП.