我有一个包含任意数据单元格的类;一种过滤器。这些单元位于后端数据存储区中。但这应该尽可能透明。
编写简单的访问器非常简单:
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
允许您挂钩未知方法的调用过程。我正在寻找一种类似于挂钩调用过程的方法,但对于所有方法,已知和未知。
答案 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]
但这对我来说更像是一个好奇而不是一个问题。 : - )
感谢您的帮助和建议!