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__
のバリアントを介して行われます。)
このような:
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"
これはまさに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
次のように、プロキシモジュールを介して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...
これはメタプログラミング手法で行うことができます。以下に例を示します。
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
Ruby 1.9以降でのみ機能します
更新:また、ブロックを使用することもできません。つまり、インスタンスメソッドに収量がありません。
私は知らない、なぜ私が反対票を投じられたのか-しかし、適切な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()
メタプログラミングで可能です。
別の選択肢は Aquarium です。 Aquariumは、Ruby用のアスペクト指向プログラミング(AOP)を実装するフレームワークです。 AOPを使用すると、通常のオブジェクトとメソッドの境界を越えて機能を実装できます。すべてのメソッドにプレアクションを適用するユースケースは、AOPの基本的なタスクです。