У меня есть класс, который обертывает ячейки произвольных данных; своего рода фильтр. Ячейки живут в серверном хранилище данных. но это должно быть как можно более прозрачным.

Написание простых аксессуаров достаточно просто:

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

Но в сочетании с средством доступа чтения контроль над операцией перешел за меня, потому что возвращаемое им свойство не само по себе. ( То есть , если данные серверной части являются массивом, я возвращаю его копию , и эта копия изменяется и никогда не отправляется мне.)

Это возможно? Если да, то как я могу это сделать? (Вероятно, это до боли очевидно, и я просто скучаю по нему, потому что устал - а может, и нет. :-)

Благодарность!

< Сильный > [ редактировать ]

Другими словами .. #method_missing позволяет вам подключиться к процессу вызова для неизвестных методов. Я ищу способ подключиться к процессу вызова аналогичным образом, но для всех методов, известных и неизвестных.

Благодарность!

3
RoUS 30 Авг 2011 в 00:51

2 ответа

Лучший ответ

Я решил это заимствованием из модуля Delegator. (Приведенный ниже код не гарантированно работает; некоторые детали я отредактировал вручную. Но он должен передать суть.)

  • При выборке (средство доступа для чтения) аннотируйте значение, которое нужно передать обратно, с помощью модифицированных методов:

    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]

Но сейчас для меня это скорее любопытство, чем проблема. :-)

Спасибо за помощь и предложения!

0
RoUS 6 Сен 2011 в 17:39

Вам нужно будет обернуть каждый объект, возвращаемый вашим классом, в мета-объект, который знает о бэкэнд и может обновлять его по мере необходимости.

В вашем примере вам нужно будет вернуть объект-оболочку массива, который может обрабатывать вставки, удаления и т. Д.

--- Редактировать ---

Вместо того, чтобы создавать множество классов-оболочек, вы можете добавить «одноэлементный метод» к возвращаемым объектам, особенно если вы можете легко определить методы, которые могут нуждаться в особой обработке.

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).

3
AShelly 29 Авг 2011 в 22:24