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