人力検索はてな
モバイル版を表示しています。PC版はこちら
i-mobile

Rubyについて質問です。
ベースとなるオブジェクトに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のメソッドが見えていないようで、ダメみたいです。
これを上手くやれる方法はあるでしょうか?


●質問者: 111
●カテゴリ:ウェブ制作
○ 状態 :終了
└ 回答数 : 2/2件

▽最新の回答へ

1 ● a-kuma3
●160ポイント

こんな感じかなあ。

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


うーん、書いては見たものの、とても気持ちが悪いです。
うまく言い表せられないけど、クラス設計的に破たんした感じが。

全てのメソッドにログを仕込む、とか、本来の処理ではないような補助的な動作を付け加えるくらいなら、まあ良いかなあ...


111さんのコメント
なるほど…これは簡単な書き方で助かります。 確かになんだか、クラスの関係がモヤモヤした感じはする…かも。 ありがとうございます。 Ideone.comも始めて知りました…。

111さんのコメント
こちらも試してみた所、きちんと動きまして、感動です。 確かに結局、moduleの方で全部の処理が動く事になって、何かこう…背中が寒くなる感じはするかも? 強引かも知れませんが、短い記法が好きです。

2 ● いわどん
●140ポイント ベストアンサー

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メソッドの再定義にも反応してしまい、無限ループに陥るのを防ぐためのものです。

他にもっと良い方法があると思いますが、今のところこんな感じで。


a-kuma3さんのコメント
あ、そうか。 instance_eval を使うと、private の壁が乗り越えられるんですね。

111さんのコメント
ありがとうございます! なにか記法一つで簡単にできる…と思っていたのですが、どうも複雑になってしまうのですね… 無理筋なのでしょうか… ちょっと私には理解が難しそうですが、試してみます。

いわどんさんのコメント
もともと無かった機能の実現というのは意外と泥臭いものかも、と思います。 ただ一度行った実装は別ファイルに分離してrequireすれば、次回からはincludeのみで実現できるわけですから、結局「記法一つ」になると思います。 屁理屈かもしれませんが、Railsや便利なgemも結局のところ同じかもしれませんしね。 ところで、今回のようなメソッドにフックをかけるような機能は実は以前から話題になっているようで、「Ruby フック」「Ruby AOP」などのキーワードでググるといくつかの実装例やgemを見つけることができますので、ご参考ください。

111さんのコメント
時間を取ってじっくり見てみた所、見た目は複雑ですが、やってる事は理解できました。 答えだけでなく、その先に繋がる調べ方まで教えて頂いてありがとうございます! 調べてみますね。
関連質問

●質問をもっと探す●



0.人力検索はてなトップ
8.このページを友達に紹介
9.このページの先頭へ
対応機種一覧
お問い合わせ
ヘルプ/お知らせ
ログイン
無料ユーザー登録
はてなトップ