【问题标题】:Ruby 1.8.7: intercepting chained methods for objectRuby 1.8.7:拦截对象的链式方法
【发布时间】:2011-08-29 20:51:39
【问题描述】:

我有一个封装任意数据单元的类;一种过滤器。这些单元存在于后端数据存储中。但这应该尽可能透明。

编写简单的访问器很简单:

def foo
  # fetch backend cell value and return it
end
def foo=(val)
  # store val in backend cell
end

我发现棘手的部分是拦截和跟踪方法,如果数据没有被包装,这些方法通常会影响数据。例如,如果数据是一个数组,obj.foo << 17原位向数组添加一个元素。我想在后端存储的数据上保持这种行为(obj.foo << 17 导致存储的值也添加了一个元素)。我想也许method_missing 会有所帮助:

def method_missing(meth, *args)
  methsym = meth.to_sym
  curval = self.get
  lastval = curval.clone
  opresult = curval.__send__(methsym, *args)
  if (curval != lastval)
    self.set(curval)
  end
  return opresult
end

但是结合阅读器访问器,操作的控制已经超出了我的范围,因为它返回的东西不是东西本身。 (,如果后端数据是一个数组,我将返回它的一个副本,它是被修改的副本,并且从未发回给我。)

这可能吗?如果是这样,我该怎么做? (这可能很明显,我只是想念它,因为我累了——或者可能不是。:-)

谢谢!

[编辑]

换句话说..#method_missing 允许您挂钩到未知方法的调用过程。我正在寻找一种方法来类似地挂接到调用过程中,但对于 所有 方法,已知 未知。

谢谢!

【问题讨论】:

    标签: ruby methods method-chaining


    【解决方案1】:

    您需要将您的类返回的每个对象包装在一个元对象中,该对象知道后端,并且可以根据需要对其进行更新。

    在您的示例中,您需要返回一个数组包装对象,该对象可以处理插入、删除等操作。

    --- 编辑---

    您可以向返回的对象添加“单例方法”,而不是创建大量包装类,特别是如果您可以轻松识别可能需要特殊处理的方法。

    module BackEndIF
      alias :old_send :__send__
      def __send__ method, *args
        if MethodsThatNeedSpecialHandling.include?(method)
           doSpecialHandling()
        else
          old_send(method,args)
        end
      end
    end
    
    #in your class:
    def foo
       data = Backend.fetch(query)
       data.extend(BackEndIF)
       return data
    end
    

    我认为任何基于方法缺失的方法都行不通,因为您返回的对象确实具有相关方法。 (即数组确实有一个运算符

    或者,也许您可​​以使用method_missing 做一些事情,就像您概述的那样。 创建一个类似这样的元对象:

    class DBobject
       def initialize(value, db_reference)
          @value = value
          @ref = db_reference
        end
       def method_missing(meth, *args)
         old_val = @value
         result = @value.__send__(meth, *args)
         DatabaseUpdate(@ref, @value) if (@value != old_val)
         return result   
       end
    end
    

    然后foo 返回一个DBObject.new(objectFromDB, referenceToDB)

    【讨论】:

    • 嗯。我希望有比这更好的东西,因为我希望后端尽可能保持透明。我需要在没有读取访问器的情况下调查 method_missing。谢谢!
    • #extend 技术看起来很有希望,但在 Ruby 1.8.7 中,#__send__ 似乎并没有被所有定义的方法调用。 ,如果Foo 有一个方法#barFoo.new.bar 将不会通过Foo 中定义的任何#__send__
    • Delegator 似乎不太可能,至少开箱即用。它似乎没有通过#kind_of? 测试; “SimpleDelegator.new([]).kind_of?(Array) => false”。它拥有Array 的所有方法,但不承认(,假的)继承。
    • 我有一个解决方案可以综合所有这些方面;我将在今天晚些时候将其添加为已接受的答案。 @AShelly 谢谢!!!
    【解决方案2】:

    我通过借用 Delegator 模块解决了这个问题。 (下面的代码保证可以工作;我已经手动编辑了一些细节。但它应该提供要点。)

    • 在 fetch(读取器访问器)上,使用修改后的方法注释要传回的值:

      def enwrap(target)
        #
        # Shamelessly cadged from delegator.rb
        #
        eigenklass = eval('class << target ; self ; end')
        preserved = ::Kernel.public_instance_methods(false)
        preserved -= [ 'to_s', 'to_a', 'inspect', '==', '=~', '===' ]
        swbd = {}
        target.instance_variable_set(:@_method_map, swbd)
        target.instance_variable_set(:@_datatype, target.class)
        for t in self.class.ancestors
          preserved |= t.public_instance_methods(false)
          preserved |= t.private_instance_methods(false)
          preserved |= t.protected_instance_methods(false)
        end
        preserved << 'singleton_method_added'
        target.methods.each do |method|
          next if (preserved.include?(method))
          swbd[method] = target.method(method.to_sym)
          target.instance_eval(<<-EOS)
            def #{method}(*args, &block)
              iniself = self.clone
              result = @_method_map['#{method}'].call(*args, &block)
              if (self != iniself)
                #
                # Store the changed entity
                #
                newklass = self.class
                iniklass = iniself.instance_variable_get(:@_datatype)
                unless (self.kind_of?(iniklass))
                  begin
                    raise RuntimeError('Class mismatch')
                  rescue RuntimeError
                    if ($@)
                      $@.delete_if { |s|
                        %r"\A#{Regexp.quote(__FILE__)}:\d+:in `" =~ s
                      }
                    end
                    raise
                  end
                end
                # update back end here
              end
              return result
            end
          EOS
        end
      end                         # End of def enwrap
      
    • 在存储(写入器访问器)上,剥离我们添加的单例方法:

      def unwrap(target)
        remap = target.instance_variable_get(:@_method_map)
        return nil unless (remap.kind_of?(Hash))
        remap.keys.each do |method|
          begin
            eval("class << target ; remove_method(:#{method}) ; end")
          rescue
          end
        end
        target.instance_variable_set(:@_method_map, nil)
        target.instance_variable_set(:@_datatype, nil)
      end                        # End of def unwrap
      

    因此,当请求该值时,它会在返回之前添加“包装器”方法,并且在将任何内容存储到后端之前删除单例。任何更改值的操作也会作为副作用更新后端。

    目前实施的这种技术一些不幸的副作用。假设带有包装变量的类在backend 中实例化,并且其中一个变量是通过ivar_foo 访问

    backend.ivar_foo
    => nil
    backend.ivar_foo = [1, 2, 3]
    => [1,2,3]
    bar = backend.ivar_foo
    => [1,2,3]
    bar << 4
    => [1,2,3,4]
    backend.ivar_foo = 'string'
    => "string"
    bar
    => [1,2,3,4]
    backend.ivar_foo
    => "string"
    bar.pop
    => 4
    bar
    => [1,2,3]
    backend.ivar_foo
    => [1,2,3]
    

    但这对我来说更多的是好奇而不是问题。 :-)

    感谢您的帮助和建议!

    【讨论】:

      猜你喜欢
      • 2011-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-23
      • 1970-01-01
      相关资源
      最近更新 更多