开发者

Rails.cache.fetch exception: TypeError (<ModelName> can't be referred to)

I'm implementing some caching by using the nifty Rails.cache.fetch. However, in one particular instance, sometimes I encounter an exception:

TypeError in EmloController#index

Emlo can't be referred to

app/controllers/emlo_controller.rb:320:in `get_employees'
app/controllers/emlo_controller.rb:356:in `prepare_json_response'
app/controllers/emlo_controller.rb:23:in `block (2 levels) in index'
app/controllers/emlo_controller.rb:15:in `index'

It seems the fetch will always explode (with the above) on the first try, and then work fine as long as the fetch is within the expiration. I know I'm missing something, so a fresh pair of eyes would be nice.

Here's the method which invokes the cache fetch:

def get_employees

  # This is for a AJAX refresh loop, so a 5-second cache actually helps quite a bit
  Rails.cache.fetch('emlo_all', :expires_in => 5.seconds, :race_condition_ttl => 1) do

    conditions = (params[:id]) ? {:user_id => params[:id]} : nil

    selections = [
      'employee_locations.id AS emlo_id',
      'employee_locations.status_id',
      'employee_locations.notes',
      'employee_locations.until',
      'employee_locations.updated_at',
      'employee_locations.user_id',
      'location_states.id AS state_id',
      'location_states.title AS status_string',
      'location_states.font_color',
      'location_states.bg_color',
      'users.displayname',
      'users.email',
      'users.mobile',
      'users.department',
      'users.extension',
      'users.guid',
      'users.dn'
    ].join(', ')

    Emlo.all(
        :select => selections,
        :joins => 'LEFT JOIN u开发者_如何学Gosers ON employee_locations.user_id=users.id LEFT JOIN location_states ON employee_locations.status_id=location_states.id',
        :conditions => conditions,
        :order => 'users.displayname ASC'
    )
  end
end


This problem arises in development mode when config.action_controller.perform_caching = true AND config.cache_classes = false -- it seems ActiveRecord objects cannot be stored with Rails.cache.

But if you need to enable config.action_controller.perform_caching in development mode for testing caching, then you must also enable config.cache_classes. This would be temporary, though, because then you'd have to restart the development server after changing classes or files in the asset pipeline.

With caching disabled, I would use Rails.cache.write(some_name, some_value) if Rails.env.production? to prevent caching from blowing up in development. Rails.cache.read() doesn't seem to be affected.


I encountered something similar storing an array of instances of a class via Rails.cache.fetch(...) do - it'd work fine until I changed any code (causing the app to be reloaded), then throw "...can't be referred to".

As @Matthew Clark mentions above it seems to be to do with the class being auto-reloaded because config.cache_classes is false in dev mode - this causes the objects stored with Rails.cache.fetch to be stale since they're effectively of a different class to the newly reloaded one.

In my case the class was a trivial one so I was able to move it to the lib directory so it's not auto-reloaded and require it in my code. If this wasn't feasible, perhaps you could do something like Rails.cache.fetch(... force: !Rails.configuration.cache_classes) if you didn't mind the extra overhead in dev mode.


Today, you can write a wrokarround following the idea of rails guides: https://guides.rubyonrails.org/classic_to_zeitwerk_howto.html#before-remove-const

Add into a initializer file the following code:

if Rails.env.development?
  unless Rails.application.config.cache_classes
    Rails.autoloaders.main.on_unload do |klass, _abspath|
      Rails.cache.clear
    end
  end
end

When some class must be reloaded then you clear the cache, and it will not fails, the only side effect will be a milliseconds more each time you change your code, and with every change, cache must be reloaded too.


Depending on the structure of your application, you might get an error in development like this: TypeError (User can't be referred) This error is caused by some caching-reloading madness: The middleware implanted by some gem is cached. But in development, your classes usually aren't. Thus some classes may not be available under certain circumstances, e.g. if you are using before filters for user authentication provided by some engine. You should be able to get rid of the error above by turning on class caching. Try it (and restart the server afterwards):

development.rb

config.cache_classes = true

If the error is gone, you're lucky. But since it is not feasible to cache classes in development, turn off class caching again and explicitly require the class that couldn't be referred. I.E.:

top of development.rb

require 'app/models/user'

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜