Rails 4.0 expire_fragment/cache expiration not wor

2019-01-11 01:19发布

I have been trying to use the caching capabilities of rails, but I am unable to expire some cache fragment although they seem to expire. Using the 'Russian Doll Caching' as pointed out in the rails tutorial site, I am using this configuration

<% cache "all_available_releases" do %>
 <% @releases.each do |release| %>
  <% cache(release) do %>
   <html code with>
   <%ruby code @release.name blah blah blah%>
  <%end%>
 <%end%>
<%end%>    

I expire the outer caching in the release_controller.rb controller, where I use expire_fragment("all_available_releases") to expire the fragment. I use it in every method of the controller that updates or deletes or adds an entry.

This is the log of WEBrick, where although the expire fragment gets registered, 5 lines later the expired fragment is read and used while it shouldn't. This example is after a destroy call.

Processing by ReleasesController#destroy as HTML
  Parameters: {"authenticity_token"=>"***/***/********************+********=", "id"=>"2"}
  Release Load (0.1ms)  SELECT "releases".* FROM "releases" WHERE "releases"."id" = ? LIMIT 1  [["id", "2"]]
   (0.1ms)  begin transaction
  SQL (2.0ms)  DELETE FROM "releases" WHERE "releases"."id" = ?  [["id", 2]]
   (148.0ms)  commit transaction
Expire fragment views/all_available_releases (0.1ms)
Redirected to http://127.0.0.1:3000/releases
Completed 302 Found in 180ms (ActiveRecord: 150.2ms)


Started GET "/releases" for 127.0.0.1 at 2013-07-03 13:09:51 +0300
Processing by ReleasesController#index as HTML
Read fragment views/all_available_releases/41cb0a928326986f35f41c52bb3d8352 (0.1ms)
  Rendered releases/index.html.erb within layouts/application (0.6ms)
Completed 200 OK in 5ms (Views: 4.0ms | ActiveRecord: 0.0ms)

I even tried using Rails.cache.delete("all_available_releases") and it didn't work either.

if I delete <%cache "all_available_releases"%> (and one <%end%>) from my html.erb the caching works fine and gets expired whenever it should.

4条回答
Emotional °昔
2楼-- · 2019-01-11 01:32

Jbuilder don't support the skip_digest. After way to many failed approaches, I decided to share my answers here as it's highly related although not with a rails view as is the issue above.

Here's a related Q/issue where DHH essentially tells the guy that he can't expire fragment_caches explicitly. https://github.com/rails/cache_digests/issues/35 Everything isn't square so here's a way around this:

class MenuController
  def index
    json = Rails.cache.fetch('clients') do
      @items = Menu.all
      render_to_string( template: 'menu/index', locals: {items: @items})
    end
    render json: json
  end
end

then you can explictly expire this anywhere, like in an observer

class MenuCacheObserver < ActiveRecord::Observer
  observe :menu, :menuitem, :menusubnavigation

  def after_save obj
    Rails.cache.delete(:clients)
  end
end

In a few cases this may make sense. On a general note, in most cases you should be using the object in the cache input, like json.cache! @my_object do wrapping the jbuilder view. That way it would invalidate when updated_at on the object changes.

查看更多
放我归山
3楼-- · 2019-01-11 01:35

Just ran into this issue myself and the way I approached dealing with it was through regular expressions. It might not be the most elegant solution but it works fine.

ActionController::Base.new.expire_fragment(%r{offer_#{@offer.id}/*})

Adding the skip_digest is much nicer though.

查看更多
叛逆
4楼-- · 2019-01-11 01:36

In Rails 5 I took the following steps to bust the cache without resorting to skip_digest: true. Our problem was that changing the value of I18n strings does not reflect in the computed cache digest so the cache would not get busted automatically.

Here is the view where the cache block is defined:

/ views/layouts/_footer.html.slim
- cache :footer do
  span= t('shared.footer')

Then in rails console I run:

fragment = ActionController::Base.new.view_context.cache_fragment_name(:footer, virtual_path: 'layouts/_footer.html.slim')
ActionController::Base.new.expire_fragment(fragment)

cache_fragment_name will figure out the digest based on the virtual_path keyword argument.

查看更多
Viruses.
5楼-- · 2019-01-11 01:37

I believe the issue is that when you cache the fragment in your view, a cache digest is being added to the cache key (views/all_available_releases/41cb0a928326986f35f41c52bb3d8352), but expire_fragment is not using the digest (views/all_available_releases).

If you add skip_digest: true to the cache call in the view it should prevent the digest from being used.

<% cache "all_available_releases", skip_digest: true do %>
 <% @releases.each do |release| %>
  <% cache(release) do %>
   <html code with>
   <%ruby code @release.name blah blah blah%>
  <%end%>
 <%end%>
<%end%>

Cache digests are only intended to be used with automatic cache expiration. If you need to manually expire cache keys then you can't use cache digests.

查看更多
登录 后发表回答