As part of my Rails 3 upgrade I am focusing on performance in my app.  The most obvious place to start is caching.  I have a few action caches scattered here in my app, but I neglected to use sweepers in order to cache just about everything.  Rails Sweepers allow you to observe an object in a controller method and expire cache if something changes in your model that warrants a cache expiry.  If your Rails site is mainly a CMS that is 90%+ read-only, you will greatly benefit from caching in Rails.  My app is a public facing read-only site, except for back-end administration stuff.  We add/update/delete content maybe 50-100 times, but most of the content is on pages that can be cached separately.  As a result, we will see a huge performance increase by using action and fragment caching.  It does not look like a whole lot has changed from Rails 2 to 3 according to the edge guides, so you can probably use these techniques on both.

Note: I am using the default caching mechanism that ship with Rails 3 beta4.  Since there are so many out-of-date plugins right now that are not fully compatible with Rails 3, I figured I would not use a plugin for caching.

Types of Caching in Rails

  1. Page Caching – caches page and bypasses application server
  2. Action Caching – caches an action, but still runs through the application server.  application_controller.rb is still executed, but the action in your specific controller is cached
  3. Fragment Caching – caches a fragment of code.  Executes application_controller.rb, widgets_controller.rb.  The caching happens in the view only.

I can’t use Page Caching in my app, because we have some users that login to an account.  If you try page caching with an app like this, then your users will not be able to see dynamic elements of the app that relate to their user account.. and even worse, you risk caching a page for a logged in user, such as an administrator.  Since my app is public facing and has admin accounts, this is not an option.  enter Action Caching…

Action Caching

Action caching is probably your best bet if you have an app with public and private pages.  Action caching will allow you to detect if a user is logged in and decide to cache or not cache.  Lets start with the basics of an action cache:

This will cache your show page, but the cool thing is that it caches each individual object.  So you will have a separate cache for  http://yourapp.com/widgets/1 AND   http://yourapp.com/widgets/2.  This is great, but we are still caching the same page for users who are logged in and users who are not logged in.  This will cause problems, so we need to add a condition to caches_action and a convenience method in the application_controller.  Take a look:

In the application controller, I setup my @user variable based on whether they are logged in. Then I added a cacheable? method. If the user is blank (meaning not logged in), then the action is cacheable, otherwise it is not cacheable (because the user is logged in). Then I am able to use the cacheable? method in the widgets_controller like so:


caches_action allows you to use an if condition. If the :if condition is true, then it caches the action. You must pass a Proc object so that the code is executed at runtime. If this method did not use a Proc, then the code would be executed when the rails code initializes on app server startup.. so basically it would return the same thing every time and it would be useless. The “x” in proc just uses the current object, which is your widgets controller… so the Proc has access to the cacheable? method. Now your actions will be cached if a user is not logged in.  This is a plus for my app, because most of the people that visit my site are public users.  That is great and all, but what if something changes on one of my pages??  I don’t want to use a cached version.  The content on the page will not be fresh.  So what do I do??  This is where sweepers come in to play. Rails Sweeper – A class that observes a model for changes and expires cache based on what changes occur.  You can tell the Sweeper what cache to expire for each type of change. Here is an example class that will show you whats up:
This class is a subclass of ActionController::Caching::Sweeper, which provides it with all the necessary mechanisms to access your controller code and expire the cache.  Notice the first line after the class definition.  You have to include this, so the sweeper knows which Model class to keep an eye on.  Next you see the callback methods.. after_update, etc..  Basically this method says, after you update a Widget instance (widget),  run this code.  The widget variable is the specific widget object that you just updated.  So if you updated widget with the id of 7, you would have access to that variable, just as if you had written “widget = Widget.find(7)”.   This class also has a private method called “expire_cache_for” that all of the callbacks use.  You don’t have to use this private method and you can put any code you want inside the callbacks.  Most of the time it makes sense to use a private method that expires multiple action caches, though.  So in this example, if you create, update or destroy a method, the cache will be expired for that particular object’s show page and the index page as well.

I was wondering where to put the sweeper when I was coding it.  Since I am using Rails 3, I just created a new directory inside myapp/app/ called “sweepers”.  I drop all of my sweepers in here and Rails 3 picks up these classes automatically.  No matter where you place it, you want to make sure these classes get loaded when your server starts.

The sweeper classes are only half of the equation though when dealing with sweeping cache.  You must also add a line to the controller where you want to use the sweeper.  In this example it makes sense to add it to the “widgets_controller.rb”, since that is where the widgets get created,updated, and destroyed.  Take a look at my example:

I added “caches_sweeper” to my WidgetsController. This method takes the sweeper name (WidgetSweeper or :widget_sweeper), and an optional : only parameter. I made the mistake of trying to use : except instead of : only.  caches_sweeper does not know what : except is, so don’t even bother trying.  I’ll put that one on my Rails wishlist.  : only tells the cache sweeper class to only run on these actions.  If you do not use : only, then the sweeper will run on every action in the controller. So thats all you have to do to use action caching with sweeping.   Now lets learn about fragment caching….

Fragment Caching

Now that you have your public facing pages cached, you think to yourself… why should the public have all the fun??  What about my admins that are logged into the system?  Do they get no $cache love?? There are bound to be parts of the application that your logged in users can share, which are not user-specific.  This is where you can use fragments.  Since you don’t want to cache the entire action, you can cache parts of the view.  The only downside to this is that your controller action will be fully executed.  This will probably not be much of a problem if you are using the new Rails 3 query API.  It has lazy loading features built in, so that the database call is only used when it is needed.  If you are just setting up an object or collection without calling any methods on it, the lazy loading feature will not make a database call until the view or helper uses that instance variable.  So my point is, fragment cache in the view will not result in any unnecessary database calls in Rails 3.  Fragment caching is pretty simple.  Take a look at this example html.erb page:

This is about all there is to a fragment cache. You use a block and pass the name to it. If you don’t provide a name to the cache, then it will use “/controller/action” as the default name. You can also get fancy and pass a hash instead like this:

This is helpful if you have more than one fragment in a view and you want to expire them separately. action_prefix will append to the end of the name so you keep them separate. You expire fragment cache in a similar manner to the action caches. Just pass the name or a hash… like so:

Now its time to get crazy with this. My users that are logged in should not cache the same fragments as the non-account users. A helper can achieve that. Check it out:

You can use this helper in place of “cache” in your view like so:

This will use a cache method if the user is logged in, and just yield the block if they are not logged in. So only our logged-in users create and use this cache.

If you want to get really crazy, you can probably cache on a per user basis by appending their user_id to the name. I haven’t tried this and it would probably result in a full cache store for a site with a large user base… but there are endless possibilities when you use helpers with these cache blocks. Same goes for the Proc stuff in action cache.

Sweeping can also be used with the fragment cache and you can use an existing sweeper. Just insert the expire_fragment calls where needed.

So thats about it. These are good techniques to speed up your app without sacrificing fresh content.  A word of caution though:  Analyze your application before and after you add caching.  You need to figure out exactly where the cache expiration should happen and which pages need action vs. fragment caching.  You don’t want to risk stale content on a page because you did not look at all the use cases.  There is no real science to figuring this out.  You just need to look at all of your models and controllers carefully to decide where to use it and think of any odd cases that require cache expiration that may not relate to the same model/controller.  A good example is a storefront with the newest products on the home page.  In this example, you would not only need to expire cache on the product pages, but also the homepage to keep it up to date with any changes & additions that may occur to the products.

So does cache = $cash?? Hell yeah it does. Less database calls = Less CPU cycles = more users on less hardware = less money spent on hosting.