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のメソッドが見えていないようで、ダメみたいです。
これを上手くやれる方法はあるでしょうか?

回答の条件
  • 1人5回まで
  • 登録:
  • 終了:2013/06/09 05:32:54
※ 有料アンケート・ポイント付き質問機能は2023年2月28日に終了しました。

ベストアンサー

id:iwadon No.2

回答回数60ベストアンサー獲得回数13

ポイント140pt

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

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

他2件のコメントを見る
id:iwadon

もともと無かった機能の実現というのは意外と泥臭いものかも、と思います。
ただ一度行った実装は別ファイルに分離してrequireすれば、次回からはincludeのみで実現できるわけですから、結局「記法一つ」になると思います。
屁理屈かもしれませんが、Railsや便利なgemも結局のところ同じかもしれませんしね。

ところで、今回のようなメソッドにフックをかけるような機能は実は以前から話題になっているようで、「Ruby フック」「Ruby AOP」などのキーワードでググるといくつかの実装例やgemを見つけることができますので、ご参考ください。

2013/06/08 18:52:00
id:gamecome

時間を取ってじっくり見てみた所、見た目は複雑ですが、やってる事は理解できました。

答えだけでなく、その先に繋がる調べ方まで教えて頂いてありがとうございます! 調べてみますね。

2013/06/09 05:29:53

その他の回答1件)

id:a-kuma3 No.1

回答回数4968ベストアンサー獲得回数2151

ポイント160pt

こんな感じかなあ。

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


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

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

id:gamecome

なるほど…これは簡単な書き方で助かります。
確かになんだか、クラスの関係がモヤモヤした感じはする…かも。

ありがとうございます。
Ideone.comも始めて知りました…。

2013/06/08 16:31:05
id:gamecome

こちらも試してみた所、きちんと動きまして、感動です。
確かに結局、moduleの方で全部の処理が動く事になって、何かこう…背中が寒くなる感じはするかも?

強引かも知れませんが、短い記法が好きです。

2013/06/09 05:31:33
id:iwadon No.2

回答回数60ベストアンサー獲得回数13ここでベストアンサー

ポイント140pt

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

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

他2件のコメントを見る
id:iwadon

もともと無かった機能の実現というのは意外と泥臭いものかも、と思います。
ただ一度行った実装は別ファイルに分離してrequireすれば、次回からはincludeのみで実現できるわけですから、結局「記法一つ」になると思います。
屁理屈かもしれませんが、Railsや便利なgemも結局のところ同じかもしれませんしね。

ところで、今回のようなメソッドにフックをかけるような機能は実は以前から話題になっているようで、「Ruby フック」「Ruby AOP」などのキーワードでググるといくつかの実装例やgemを見つけることができますので、ご参考ください。

2013/06/08 18:52:00
id:gamecome

時間を取ってじっくり見てみた所、見た目は複雑ですが、やってる事は理解できました。

答えだけでなく、その先に繋がる調べ方まで教えて頂いてありがとうございます! 調べてみますね。

2013/06/09 05:29:53

コメントはまだありません

この質問への反応(ブックマークコメント)

「あの人に答えてほしい」「この質問はあの人が答えられそう」というときに、回答リクエストを送ってみてましょう。

これ以上回答リクエストを送信することはできません。制限について

回答リクエストを送信したユーザーはいません