Developer:Caching
From myExperiment
Caching
By Tom Eveleigh, 18th September 2008
Contents |
Caching in myExperiment
myExperiment uses fragment caching in a number of places on the site, including the sidebar, the homepage, and the listings of workflows, files, packs and groups.
The path where the cache is stored is configurable and will be referred to here as [cache_path]. The default location is 'tmp/cache/hostname'.
Sidebar
Four sections of the sidebar are cached for each logged-in user as they are specific to the user. They are:
- the user monitor, stored in [cache_path]/sidebar_cache/user_monitor/user_id.cache,
- the asset manager, stored in [cache_path]/sidebar_cache/asset_manager/user_id.cache,
- the user favourites, stored in [cache_path]/sidebar_cache/user_favourites/user_id.cache,
- and the user tags, stored in [cache_path]/sidebar_cache/user_tags/user_id.cache
The popular tags section is cached once and served to all users as it is not user specific, and is stored in [cache_path]/sidebar_cache/tags.part=most_popular_tags.cache
Homepage
Five sections of the homepage are cached for each logged-in user as they are specific to the user. They are:
- the list of most recently updated items, stored in [cache_path]/home_cache/updated_items/user_id.cache,
- the latest groups, stored in [cache_path]/home_cache/latest_groups/user_id.cache,
- the latest comments, stored in [cache_path]/home_cache/latest_comments/user_id.cache,
- the latest reviews, stored in [cache_path]/home_cache/latest_reviews/user_id.cache,
- and the latest tags, stored in [cache_path]/home_cache/latest_tags/user_id.cache
The announcements section is cached once and served to all users as it is not user specific, and is stored in [cache_path]/home_cache/announcements.cache
Listing Pages
On the listing pages for workflow, files, packs and groups, a cache fragment is created for each object listed on the page and is stored in:
- Workflows - [cache_path]/workflows_cache/listing/workflow_id.cache
- Files - [cache_path]/files_cache/listing/blob_id.cache
- Packs - [cache_path]/packs_cache/listing/pack_id.cache
- Groups - [cache_path]/groups_cache/listing/network_id.cache
A cache fragment for the tags relating to that class is also created and stored in [cache_path]/[workflows | files | packs | groups]/all_tags.cache
Miscellaneous
The page footer is also cached and stored in [cache_path]/global_cache/footer.cache
Sweepers
Sweepers are responsible for observing models and expiring cache fragments when the model changes. There is generally one sweeper per model, although one sweeper can monitor multiple models if necessary.
The sweepers are stored in the app/models/ directory and use the naming convention [model_name]_sweeper.rb. They implement the callbacks 'after_create', 'after_update' and 'after_destroy' so that they are called when the model changes.
As many of the sweepers in the code expire the same fragments, many cache expiry methods are in the module lib/caching_helper.rb, and are called from the sweepers. This avoids code repetition and keeps the system maintainable.
A Simple Example
app/views/announcements/index.html:
<% cache(:controller => 'announcements', :action => 'index') do -%>
<% announcements.each do |a| %>
<%= a.body %>
<% end -%>
<% end -%>
The above view code would cache the list of announcements in [cache_path]/announcements/index.cache
To expire this cache fragment when an announcement is added, modified or deleted, we would need the following sweeper that monitors /app/models/announcement.rb
app/models/announcement_sweeper.rb:
class AnnouncementSweeper < ActionController::Caching::Sweeper
observe Announcement
def after_create(announcement)
expire_fragment(:controller => 'announcements', :action => 'index')
end
def after_destroy(announcement)
expire_fragment(:controller => 'announcements', :action => 'index')
end
def after_update(announcement)
expire_fragment(:controller => 'announcements', :action => 'index')
end
end
The final step is for the controller wake the sweeper up when it receives a request that may alter the model.
app/controllers/announcements_controller.rb:
class AnnouncementsController < ApplicationController cache_sweeper :announcement_sweeper, :only => [ :create, :update, :destroy ] ... end
The 'cache_sweeper' line in the controller wakes the sweeper up whenever the controllers' create, update or delete methods are called. These three steps are all that is required for a simple event-based cache expiry system.
A User Specific Cache Example
If we were to alter the way announcements in the above example worked, so that each user is presented with only announcements that they have created, we would have to update the way the caching works so that one cache fragment is created for each user, as shown in this example.
app/views/announcements/index.html:
<% cache(:controller => 'announcements', :action => 'index', :id => (logged_in? ? current_user.id : 0)) do -%>
<% announcements.each do |a| %>
<%= a.body if a.user_id == (logged_in? ? current_user.id : 0) %>
<% end -%>
<% end -%>
The cache fragment is now stored in [cache_path]/announcements/index/user_id.cache, with the user_id being the id of the current user or 0 if they are not logged in.
The sweeper then needs to be updated to expire the correct cache based on the user_id of the announcement that has changed.
app/models/announcement_sweeper.rb:
class AnnouncementSweeper < ActionController::Caching::Sweeper
observe Announcement
def after_create(announcement)
expire_fragment(:controller => 'announcements', :action => 'index', :id => announcement.user_id)
end
def after_destroy(announcement)
expire_fragment(:controller => 'announcements', :action => 'index', :id => announcement.user_id)
end
def after_update(announcement)
expire_fragment(:controller => 'announcements', :action => 'index', :id => announcement.user_id)
end
end
Using an ID as the name of the cache can be extended to any type of object, for example workflows or groups.
