Object.respond_to? застрял в бесконечном цикле - PullRequest
1 голос
/ 13 марта 2012

Я создаю гем Rails 3, который существенно модифицирует записи, возвращаемые из запроса ActiveRecord.Одна из вещей, которые я делаю, это переопределение методов method_missing и respond_to?, но, похоже, мое определение respond_to? приводит к бесконечному циклу, который выдает ошибку «SystemStackError: слишком большой уровень стека».

Вот мои оригинальные определения этих методов:

def respond_to?(name, *args)
  super(name, *args) || parent_association.respond_to?(name)
end

def method_missing(name, *args, &block)
  if parent_association.respond_to?(name)
    parent_association.send(name, *args, &block)
  else
    super(name, *args, &block)
  end
end

def parent_association
  send(parent_association_name)  # Essentially yields another ActiveRecord
                                 # instance (e.g.: instance of User), but
                                 # never returns itself.
end

Пытаясь понять, почему происходил этот бесконечный цикл, я реструктурировал respond_to? с некоторыми выводами «до» и «после» впосмотрите, где он застревает.

def respond_to?(name, *args)
  return true if super(name, *args)
  puts "before (#{name})"
  result = parent_association.respond_to?(name)
  puts "after"
  result
end

При запуске кажется, что различные обратные вызовы и методы атрибутов работают, как и ожидалось, с одним вызовом до и после для каждого:

before (_run__374051839217347232__initialize__1707831318230746190__callbacks)
after
before (_run__374051839217347232__validation__1707831318230746190__callbacks)
after
before (_run__374051839217347232__validate__1707831318230746190__callbacks)
after
before (_run__374051839217347232__save__1707831318230746190__callbacks)
after
before (_run__374051839217347232__create__1707831318230746190__callbacks)
after
before (created_at)
after
before (created_on)
after
...

Однаковсякий раз, когда я вижу обратный вызов поиска, который, кажется, зацикливается на бесконечном цикле:

before (_run__374051839217347232__find__1707831318230746190__callbacks)
before (_run__374051839217347232__find__1707831318230746190__callbacks)
before (_run__374051839217347232__find__1707831318230746190__callbacks)
before (_run__374051839217347232__find__1707831318230746190__callbacks)
before (_run__374051839217347232__find__1707831318230746190__callbacks)
before (_run__374051839217347232__find__1707831318230746190__callbacks)
before (_run__374051839217347232__find__1707831318230746190__callbacks)
before (_run__374051839217347232__find__1707831318230746190__callbacks)
...
SystemStackError: stack level too deep

Если я взломаю свой respond_to?, то все будет работать гладко:

def respond_to?(name, *args)
  return true if super(name, *args)
  return false if name =~ /^_run_.*_find_.*_callbacks$/
  parent_association.respond_to?(name)
end

Что я делаю не так, что мне, кажется, нужен этот взлом?И как мне этого избежать?

Ответы [ 2 ]

0 голосов
/ 09 апреля 2012

В итоге проблема заключалась в этой функции:

def parent_association
  send(parent_association_name)  # Essentially yields another ActiveRecord
                                 # instance (e.g.: instance of User), but
                                 # never returns itself.
end

Переменная parent_association_name похожа на employee, которая определяется следующим образом:

belongs_to :employee

Потому что employee не определено в экземпляре модели, пока ПОСЛЕ того, как обратный вызов find будет выполнен, и, поскольку я впервые вызывал respond_to? в точке ДО ТОГО, как вызывается обратный вызов find (в коде no, включенном в мой исходный вопрос), вызов send(parent_assocation_name) вызывал рекурсивный вызов respond_to?.

0 голосов
/ 13 марта 2012

Если parent_association возвращает новый объект AR, он также унаследует ваш хак, поэтому вызов respond_to? для него вызовет ваш respond_to?, который создаст новый объект AR и т. Д. *

...