Fully-cacheable edit controls in Rails

Fully-cacheable edit controls in Rails

The Ruby on Rails framework provides extraordinary, deeply-integrated caching faculties. A challenge that I’ve inevitably encountered in every content-driven Rails application I’ve ever worked on is the selective exposure of admin controls in cached views.

For example, an application may have a user profile page. Most users of the application should see an identical version of this page, so it is an excellent candidate for caching. For the account owner, we may wish to expose an “Edit Account” control, or several controls throughout the page to edit various elements of the account (i.e. “Update profile picture”, “Change display name”, etc.). In this article I will introduce a robust approach to this problem that has served me well many times over.

One naive approach to this problem would be to incorporate the user’s account ownership into the cache key. This works well for fragment or action caching. Assuming we are using the CanCan gem for Authorization, we might cache our user profile view as follows:

This approach is straightforward and robust, and I would strongly recommend it for a large class of applications of moderate scale. It does not, however, carry over to reverse-proxy caching, such as with Fastly or similar, which is the most optimal caching strategy for a page like this at significant scale. For use-cases like this we need a slightly more imaginative strategy.

What I have found to work best for this use case is a strategy which pushes the logic for exposing controls client-side, but in a very minimal and robust way that leverages all the benefits of monolithic architecture.

The first step is to keep the ID of the logged in user in the cookie. Assuming we are using Devise, we can hook into the after_sign_up/in_path and after_sign_out_path callbacks, which are invoked by the framework at the end of login/registration or logout processing:

We now know that we will reliably have our logged-in user’s ID available client side in the browser cookie for any logged-in user. We can now inject some information about resource ownership into our template, which we assume will end up fully cached.

We can now tag admin-only UI elements with a designated class, and hide or reveal this with a combination of javascript and CSS. Let’s use the class admin-only for this purpose. In our stylesheet we will hide elements of this class by default:

In our javascript, we can use the info that we’ve brought into context about account ownership to selectively reveal these elements to the owner of the page (the below code assumed that you have included the js-cookie library in your project).

A more minimal implementation in the absence of Turbolinks might look like the following:

All that is left is to tag our admin-only controls with the the admin-only CSS class.

Admin controls will remain hidden by default but exposed immediately for admin users. This approach of course assumes that you have a robust authorization implementation, as URIs to admin controls are still visible were the user to inspect page markup.

Though this approach to exposing admin-controls in cached pages is slightly more involved than that of the simple incorporation of ownership information into a fragment or action cache key, it will serve you in circumstances where you need to leverage a reverse-proxy cache in your deployment.  For this use case, I believe you’ll find no more minimal and robust an approach than the integrated solution presented here.

Nicholas

Hi! I'm Nicholas and I like building stuff. I'm software development consultant in New York, where I work with startups big and small. I am also a sometime theatre and film nerd. You can reach me by email at nicholas@zaillian.com (public key here if you want to encrypt your message). I keep a personal homepage at nicholas.zaillian.com and accept consulting engagements through my company Written Software.

No Comments

Leave a Comment

Please be polite. We appreciate that.
Your email address will not be published and required fields are marked