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

时间:2011-08-29 20:51:40

标签: ruby methods method-chaining

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

编写简单的访问器非常简单:

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

我发现棘手的部分是截取和跟踪方法,如果数据没有被包装,通常会影响数据。例如,如果数据是一个数组,obj.foo << 17会向数组 in situ 添加一个元素。我想维护存储在后端的数据的行为(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

但是与阅读器访问器结合使用,对操作的控制已经超越了我,因为它返回的东西是而不是这个东西本身。 (,如果后端数据是一个数组,我正在返回它的副本,它是正在被修改的副本,从不发回给我。)< / p>

这可能吗?如果是这样,我该怎么办? (这可能是非常明显的,我只是因为我累了 - 或者可能不是因为它而错过了它。: - )

谢谢!

[编辑]

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

谢谢!

2 个答案:

答案 0 :(得分: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

我认为任何基于方法缺失的东西都不会起作用,因为你要返回的对象确实有相关的方法。 (即Array确实有一个运算符&lt;&lt;,它不会丢失)

或者,也许你可以像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)

答案 1 :(得分:0)

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

  • 在fetch(reader accessor)上,使用修改后的方法注释要传回的值:

    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
    

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

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

但这对我来说更像是一个好奇而不是一个问题。 : - )

感谢您的帮助和建议!