Ruby 1.8.7: перехват цепных методов для объекта - PullRequest
3 голосов
/ 30 августа 2011

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

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

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 позволяет подключиться к процессу вызова неизвестных методов.Я ищу способ подключиться к процессу вызова аналогичным образом, но для всех методов, известных и неизвестно.

Спасибо!

Ответы [ 2 ]

3 голосов
/ 30 августа 2011

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

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

--- Edit ---

вместосоздавая множество классов-оболочек, вы можете добавить «возвращаемый объект» к методу синглтона, особенно если вы можете легко определить методы, которые могут нуждаться в специальной обработке.Не думаю, что что-либо, основанное на пропущенном методе, будет работать, так как возвращаемые объекты действительно имеют соответствующие методы.(т. е. у массива есть оператор <<, он не пропущен) </strike>

Или, может быть, вы можете сделать что-то с 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 голосов
/ 06 сентября 2011

Я решил это, позаимствовав из модуля 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]

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

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

...