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)
。
呃。我希望得到比这更好的东西,因为我希望防守能够尽可能保持透明。我需要调查没有读取访问器的'method_missing' ..谢谢! – RoUS
'#extend'技术看起来很有希望,但在Ruby 1.8.7中,'#__ send__'似乎没有为所有定义的方法调用。 * I.e. *,如果'Foo'有一个'#bar'方法,'Foo.new.bar'不会经过'Foo'中定义的任何'#__ send__'。 – RoUS
也许'Delegator'类可以提供帮助。调查那个.. – RoUS