Rails: “Stack level too deep” error when calling “id” primary key method

纵饮孤独 提交于 2019-12-20 02:59:17

问题


This is a repost on another issue, better isolated this time. In my environment.rb file I changed this line:

config.time_zone = 'UTC'

to this line:

config.active_record.default_timezone = :utc

Ever since, this call:

Category.find(1).subcategories.map(&:id)

Fails on "Stack level too deep" error after the second time it is run in the development environment when config.cache_classes = false. If config.cache_classes = true, the problem does not occur. The error is a result of the following code in active_record/attribute_methods.rb around line 252:

def method_missing(method_id, *args, &block)
...

    if self.class.primary_key.to_s == method_name
        id
    ....

The call to the "id" function re-calls method_missing and there is nothing that prevents the id to be called over and over again, resulting in stack level too deep.

I'm using Rails 2.3.8. The Category model has_many :subcategories. The call fails on variants of that line above (e.g. Category.first.subcategory_ids, use of "each" instead of "map", etc.).

Any thoughts will be highly appreciated.

Thanks! Amit


回答1:


Even though this is solved, I just wanted to chime in on this, and report how I fixed this issue. I had the same symptoms as the OP, initial request .id() worked fine, subsequent requests .id() would throw an the "stack too deep" error message. It's a weird error, as it generally it means you have an infinite loop somewhere. I fixed this by changing:

config.action_controller.perform_caching = true
config.cache_classes                     = false

to

config.action_controller.perform_caching = true
config.cache_classes                     = true

in environments/production.rb.

UPDATE: The root cause of this issue turned out to be the cache_store. The default MemoryStore will not preserve ActiveRecord models. This is a pretty old bug, and fairly severe, I'm not sure why it hasn't been fixed. Anyways, the workaround is to use a different cache_store. Try using this, in your config/environments/development.rb:

config.cache_store = :file_store

UPDATE #2: C. Bedard posted this analysis of the issue. Seems to sum it up nicely.

Having encountered this problem myself (and being stuck on it repeateadly) I have investigated the error (and hopefully found a good fix). Here's what I know about it: It happens when ActiveRecord::Base#reset_subclasses is called by the dispatcher between requests (in dev mode only).

ActiveRecord::Base#reset_subclasses wipes out the inheritable_attributes Hash (where #skip_time_zone_conversion_for_attributes is stored). It will not only happen on objects persisted through requests, as the "monkey test app" from #1290 shows, but also when trying to access generated association methods on AR, even for objects that live only on the current request.

This bug was introduced by this commit where the #skip_time_zone_conversion_for_attributes declaration was changed from base.cattr_accessor to base.class_inheritable_accessor. But then again, that same commit also fixed something else. The patch initially submitted here that simply avoids clearing the instance_variables and instance_methods in reset_subclasses does introduce massive leaking, and the amounts leaked seem directly proportional to complexity of the app (i.e. number of models, associations and attributes on each of them). I have a pretty complex app which leaks nearly 1Mb on each request in dev mode when the patch is applied. So it's not viable (for me anyways).

While trying out different ways to solve this, I have corrected the initial error (skip_time_zone_conversion_for_attributes being nil on 2nd request), but it uncovered another error (which just didn't happen because the first exception would be raised before getting to it). That error seems to be the one reported in #774 (Stack overflow in method_missing for the 'id' method).

Now, for the solution, my patch (attached) does the following: It adds wrapper methods for #skip_time_zone_conversion_for_attributes methods, making sure it always reads/writes the value as an class_inheritable_attribute. This way, nil is never returned anymore.

It ensures that the 'id' method is not wiped out when reset_subclasses is called. AR is kinda strange on that one, because it first defines it directly in the source, but redefines itself with #define_read_method when it is first called. And that is precisely what makes it fail after reloading (since reset_subclasses then wipes it out).

I also added a test in reload_models_test.rb, which calls reset_subclasses to try and simulate reloading between requests in dev mode. What I cannot tell at this point is if it really triggers the reloading mechanism as it does on a live dispatcher request cycle. I also tested from script/server and the error was gone.

Sorry for the long paste, it sucks that the rails lighthouse project is private. The patch mentioned above is private.




回答2:


-- This answer is copied from my original post here.

Finally solved! After posting a third question and with help of trptcolin, I could confirm a working solution.

The problem: I was using require to include models from within Table-less models (classes that are in app/models but do not extend ActiveRecord::Base). For example, I had a class FilterCategory that performed require 'category'. This messed up with Rails' class caching. I had to use require in the first place since lines such as Category.find :all failed.

The solution (credit goes to trptcolin): replace Category.find :all with ::Category.find :all. This works without the need to explicitly require any model, and therefore doesn't cause any class caching problems.

The "stack too deep" problem also goes away when using config.active_record.default_timezone = :utc



来源:https://stackoverflow.com/questions/4136245/rails-stack-level-too-deep-error-when-calling-id-primary-key-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!