1

I'm building a Rails 3 gem that essentially modifies the records returned from an ActiveRecord query. One of the things I'm doing is overriding the method_missing and respond_to? methods, but it seems that my respond_to? definition is resulting in an infinite loop that is throwing a "SystemStackError: stack level too deep" error.

Here are my original definitions of these methods:

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

In trying to learn why this infinite loop was occurring, I restructured respond_to? with some "before" and "after" output to see where it's getting stuck.

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

When running, it appears that various callbacks and attribute methods run as expected, with a single before and after call for each:

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

However, any time I see a find callback, that appears to be caught in an infinite loop:

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

If I hack my respond_to?, then everything appears to run smoothly:

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

What am I doing wrong that I seem to need this hack? And how can I avoid it?

Matt Huggins
  • 81,398
  • 36
  • 149
  • 218

2 Answers2

0

The issue ended up being this function:

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

The variable parent_association_name is something like employee, which is defined via something like:

belongs_to :employee

Because employee is not defined on the model instance until AFTER the find callback executes, and because I was first calling respond_to? in a spot BEFORE the find callback is being called (in code no included in my original question), the call to send(parent_assocation_name) was causing a recursive call to respond_to?.

Matt Huggins
  • 81,398
  • 36
  • 149
  • 218
  • 1
    It would be clearer if you used precise language. `parent_association_name` is not a variable, and I think `employee` is always defined, although it might not be loaded. Not sure you actually mean "callback" either. I'm still not sure how what you are describing is not what I was saying, nor how you fixed it. – Marc-André Lafortune Apr 09 '12 at 20:05
0

If parent_association returns a new AR object, it will also inherits your hack, so calling respond_to? on it will call your respond_to?, which will create a AR new object, etc...

Marc-André Lafortune
  • 78,216
  • 16
  • 166
  • 166
  • That's not true if the other ActiveRecord object that it creates actually implements the method, which should be the case here. For example, the other AR object will have a method like `.name` (due to the attributes implemented), which the original AR object might not have had. At some point, it's just going to call `super` and return since the method is actually defined on the object. – Matt Huggins Mar 13 '12 at 14:46
  • Just to follow up, it did end up being recursion via `respond_to?`, but it wasn't an issue of the function being called being undefined recursively on separate AR objects, as you mentioned. – Matt Huggins Apr 09 '12 at 19:22