Fragment Caching, Could Be Better.

You know one thing that puzzled me about Rails fragment caching? Well, let’s first say we want to use fragment caching because some parts of our view is expensive to render, say because of a loop or some implicit find() call made by an association of a model you instanstiated in the controller action. What is puzzling is that even if you have most of the view rendered from cached fragments, the action doing the database lookups, calculations, what-not is still being executed even if all of the data it generates for the views isn’t even used!

If some part of a view that is fragment cached exclusively uses an instance variable, say @foo, then if that part of the view will render with the cached fragment, you know that you don’t need @foo anymore. Why populate it in the action? It might get its data from an expensive database call. More generally, it would be nice if certain blocks of code in the action would run only on the condition that some cached fragment does *not* exist.

Well, this is how you do it. Enter: for_fragment(). It is the analog to cache(). It takes the same arguments. The block passed to for_fragment() will only be executed if the associated cache file (determined by the arguments, it corresponds exactly to cache()) is not found.

Just put the following in some file and include it in your environment.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
module ActionController
  module Caching
    module Fragments

      # for_fragment() takes the same argument as cache(). It will execute the associated block only if the fragment
      # corresponding to this argument doesn't exist. This allows us to conditionally execute code in our controller 
      # action based on existance of cached fragments.
      #
      # For example: 
      #
      # If you have the following in your view:
      #   
      #   <% cache do %>
      #     All Topics:
      #     <%= render_collection_of_partials "topic", @topics %>
      #   <% end %>
      #
      # You can use in your controller action:
      #   
      #   for_fragment do
      #     @topics = Topic.find(:all)
      #   end
      # 
      # Now if the view is going to render from cache anyway, we save a database call b/c @topics will not be used
      # anyway at this point. 
      def for_fragment(name = {})
        raise("Block required") unless block_given?
        yield unless read_fragment(name)
      end
    end
  end
end

There’s a full example in the comments to the code above.

Enjoy.

Comments