Ruby 1.8.7:拦截对象的链接方法

问题描述:

我有一个包装任意数据单元的类;一种过滤器。这些单元存在于后端数据存储中。但是这应该尽可能透明。Ruby 1.8.7:拦截对象的链接方法

写简单的存取很简单:

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让你挂接到未知的方法调用过程。我正在寻找一种类似的方式来调用调用过程,但对于全部方法,已知的未知。

谢谢!

我通过从Delegator模块借来解决了这个问题。 (下面的代码是不保证工作;我已经编辑了手的一些细节,但它应该提供GIST)

  • 在一个获取(读取访问),注释值为传递回用改性方法:

    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 ([email protected]) 
           [email protected]_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 
    
  • 在一个存储装置(写入器存取器),剥去singleton方法我们添加:

    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 
    

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

There are这种技术的一些不幸的副作用,如目前实施。假设与包裹变量类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] 

但是,这更多的是好奇,在这段时间对我的问题。 :-)

感谢您的帮助和建议!

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

在你的榜样,你需要返回一个数组包装对象,它可以处理插入,删除等

---编辑---

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

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一样,你勾勒的东西。 创建一个单一的meta_object是这样的:

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)

+0

呃。我希望得到比这更好的东西,因为我希望防守能够尽可能保持透明。我需要调查没有读取访问器的'method_missing' ..谢谢! – RoUS

+0

'#extend'技术看起来很有希望,但在Ruby 1.8.7中,'#__ send__'似乎没有为所有定义的方法调用。 * I.e. *,如果'Foo'有一个'#bar'方法,'Foo.new.bar'不会经过'Foo'中定义的任何'#__ send__'。 – RoUS

+0

也许'Delegator'类可以提供帮助。调查那个.. – RoUS