0

クラスメソッド呼び出し、またはクラスメソッド呼び出しとインスタンスメソッド呼び出しの両方で機能するように、インスタンスメソッド呼び出しをインターセプトするために提供された回答を変更するのを手伝ってくれる人はいますか? Ruby でのメタプログラミングに関する私の限られた知識から、 を使用してどこかでシングルトン クラスを開くことに関係があると思いますがclass << self、このコードを使用してさまざまな場所でそれを試してみましたが、理解できないようですそれを出します。ただし、直接的な回答ではなく、正しい方向へのプッシュを提供していただけますか? 私は、完全に理解できない場合を除き、自分で物事を理解するのが大好きです。ありがとう!

4

1 に答える 1

2

これが、提供したリンクの回答から変更された私のソリューションです。フックロジックをスーパークラスから別のモジュールに移動して、クラスがフックを必要とするたびに、そのモジュールをインクルードまたは拡張してフックメソッドを呼び出すようにしました。

  • before_each_method type, &block-またはにtypeすることができ、ブロックは各メソッドの前に実行されるコードです。ブロックは特定の環境下で評価されます。つまり、ブロック内のメソッドはインスタンスです。クラスメソッドの場合、ブロック内はクラスです。:class:instanceselfself
  • before_class_method &block- エイリアスbefore_each_method :class, &block
  • before_instance_method &block- エイリアスbefore_each_method :instance, &block
module MethodHooker
  def self.included(base)
    base.extend(ClassMethods)
  end
  def self.extended(base)
    base.extend(ClassMethods)
  end
  module ClassMethods
    def before_each_method type, &block
      singleton = class << self; self; end
      case type
      when :instance
        this = self
        singleton.instance_eval do
          define_method :method_added do |name|
            last = instance_variable_get(:@__last_methods_added)
            return if last and last.include?(name)
            with = :"#{name}_with_before_each_method"
            without = :"#{name}_without_before_each_method"
            instance_variable_set(:@__last_methods_added, [name, with, without])
            this.class_eval do
              define_method with do |*args, &blk|
                instance_exec(name, args, blk, &block)
                send without, *args, &blk
              end
              alias_method without, name
              alias_method name, with
            end
            instance_variable_set(:@__last_methods_added, nil)
          end
        end
      when :class
        this = self
        singleton.instance_eval do
          define_method :singleton_method_added do |name|
            return if name == :singleton_method_added
            last = instance_variable_get(:@__last_singleton_methods_added)
            return if last and last.include?(name)
            with = :"#{name}_with_before_each_method"
            without = :"#{name}_without_before_each_method"
            instance_variable_set(:@__last_singleton_methods_added, [name, with, without])
            singleton.class_eval do
              define_method with do |*args, &blk|
                instance_exec(name, args, blk, &block)
                send without, *args, &blk
              end
              alias_method without, name
              alias_method name, with
            end
            instance_variable_set(:@__last_singleton_methods_added, nil)
          end
        end
      end
    end
    def before_class_method &block
      before_each_method :class, &block
    end
    def before_instance_method &block
      before_each_method :instance, &block
    end
  end
end

class Test
  extend MethodHooker
  before_each_method :instance do |method, args, block|
    p [method, args, block]
    puts "before instance method(#{method}) #{@var}"
  end
  before_class_method do |method, args, block|
    puts "before class method(#{method}) #{@class_instance_var}"
  end
  @class_instance_var = 'stackoverflow'
  def initialize
    @var = 1
  end
  def test(a, b, c)
    puts "instance method test"
  end
  def self.test1
    puts "class method test"
  end
end

Test.new.test(1, "arg2", [3]) {|t| t}
Test.test1

出力は次のようになります。

[:initialize, [], nil]
before instance method(initialize)
[:test, [1, "arg2", [3]], #<Proc:0x00000001017d5eb8@/Users/test/before_method.rb:88>]
before instance method(test) 1
instance method test
before class method(test1) stackoverflow
class method test
于 2013-06-27T07:06:26.830 に答える