ベースとなるオブジェクトにmodlueをincludeするだけで処理が追加される…という書き方をしたいのですが、上手くいきません。
例:元となるオブジェクトBaseにInertiaをincludeすると、通常のupdateに加えてInertiaのupdateが加えて処理されるようになる
module Inertia
alias :inertia_old_update :update
def update
inertia_old_update
追加処理内容
end
end
class Base
include Inertia #ここを付け加えるだけ
def update
処理内容
end
end
という風に行きたいのですが、moduleのaliasはBaseのメソッドが見えていないようで、ダメみたいです。
これを上手くやれる方法はあるでしょうか?
Baseクラスの記述を変更せずに対応するのであれば、こんな感じでどうでしょうか:
# -*- coding: utf-8 -*- module Inertia def self.included(mod) def mod.method_added(m) return if (@__once__ ||= {})[[self, m]] @__once__[[self, m]] = true self.instance_eval do alias_method :orig_update, :update define_method(:update) do p("B") orig_update end end end end end class Base include Inertia def update p("A") end end Base.new.update
ideoneでの実行例も http://ideone.com/12NXAT にあります。
まず、Inertiaモジュールをincludeした時点(Module#included
)ではまだBase#update
は定義されていませんので(Rubyでは定義は順に実行されていきます)、のちに定義されるであろうメソッドに対する準備を行います(Module#method_added
)。
その後、update
メソッドが定義された後でメソッド名を差し替え(Module#alias_method
)、新たなupdate
メソッドを定義し直しています(BasicObject#instance_eval
からの Module#define_method
)。
なお、@__once__
を参照している付近は、自分自身によるupdate
メソッドの再定義にも反応してしまい、無限ループに陥るのを防ぐためのものです。
他にもっと良い方法があると思いますが、今のところこんな感じで。
こんな感じかなあ。
module Foo def initialize @original_update = self.method(:update) def self.update @original_update.call puts "!!! Extended Implementation !!!" end end end class Bar def update puts "Bar : update" end end class Baz < Bar def update super puts "Baz : update" end end
Ideone.com で試したのがこちら。
http://ideone.com/75M2bx
うーん、書いては見たものの、とても気持ちが悪いです。
うまく言い表せられないけど、クラス設計的に破たんした感じが。
全てのメソッドにログを仕込む、とか、本来の処理ではないような補助的な動作を付け加えるくらいなら、まあ良いかなあ...
なるほど…これは簡単な書き方で助かります。
確かになんだか、クラスの関係がモヤモヤした感じはする…かも。
ありがとうございます。
Ideone.comも始めて知りました…。
こちらも試してみた所、きちんと動きまして、感動です。
確かに結局、moduleの方で全部の処理が動く事になって、何かこう…背中が寒くなる感じはするかも?
強引かも知れませんが、短い記法が好きです。
Baseクラスの記述を変更せずに対応するのであれば、こんな感じでどうでしょうか:
# -*- coding: utf-8 -*- module Inertia def self.included(mod) def mod.method_added(m) return if (@__once__ ||= {})[[self, m]] @__once__[[self, m]] = true self.instance_eval do alias_method :orig_update, :update define_method(:update) do p("B") orig_update end end end end end class Base include Inertia def update p("A") end end Base.new.update
ideoneでの実行例も http://ideone.com/12NXAT にあります。
まず、Inertiaモジュールをincludeした時点(Module#included
)ではまだBase#update
は定義されていませんので(Rubyでは定義は順に実行されていきます)、のちに定義されるであろうメソッドに対する準備を行います(Module#method_added
)。
その後、update
メソッドが定義された後でメソッド名を差し替え(Module#alias_method
)、新たなupdate
メソッドを定義し直しています(BasicObject#instance_eval
からの Module#define_method
)。
なお、@__once__
を参照している付近は、自分自身によるupdate
メソッドの再定義にも反応してしまい、無限ループに陥るのを防ぐためのものです。
他にもっと良い方法があると思いますが、今のところこんな感じで。
もともと無かった機能の実現というのは意外と泥臭いものかも、と思います。
ただ一度行った実装は別ファイルに分離してrequireすれば、次回からはincludeのみで実現できるわけですから、結局「記法一つ」になると思います。
屁理屈かもしれませんが、Railsや便利なgemも結局のところ同じかもしれませんしね。
ところで、今回のようなメソッドにフックをかけるような機能は実は以前から話題になっているようで、「Ruby フック」「Ruby AOP」などのキーワードでググるといくつかの実装例やgemを見つけることができますので、ご参考ください。
時間を取ってじっくり見てみた所、見た目は複雑ですが、やってる事は理解できました。
答えだけでなく、その先に繋がる調べ方まで教えて頂いてありがとうございます! 調べてみますね。
もともと無かった機能の実現というのは意外と泥臭いものかも、と思います。
2013/06/08 18:52:00ただ一度行った実装は別ファイルに分離してrequireすれば、次回からはincludeのみで実現できるわけですから、結局「記法一つ」になると思います。
屁理屈かもしれませんが、Railsや便利なgemも結局のところ同じかもしれませんしね。
ところで、今回のようなメソッドにフックをかけるような機能は実は以前から話題になっているようで、「Ruby フック」「Ruby AOP」などのキーワードでググるといくつかの実装例やgemを見つけることができますので、ご参考ください。
時間を取ってじっくり見てみた所、見た目は複雑ですが、やってる事は理解できました。
2013/06/09 05:29:53答えだけでなく、その先に繋がる調べ方まで教えて頂いてありがとうございます! 調べてみますね。