padrino/padrino-framework

View on GitHub
padrino-cache/README.rdoc

Summary

Maintainability
Test Coverage
= Painless Page and Fragment Caching (padrino-cache)

== Overview

This component enables caching of an application's response contents on
both page- and fragment-levels. Output cached in this manner is
persisted, until it expires or is actively expired, in a configurable store
of your choosing. Most popular key/value stores work out of the box. Take a look
at the {Moneta documentation}[http://rubydoc.info/gems/moneta] for a list of all supported stores.

== Caching Quickstart

Padrino-cache can reduce the processing load on your site very effectively
with minimal configuration.

By default, the component caches pages in a file store at <tt>tmp/cache</tt>
within your project root. Entries in this store correspond directly
to the request issued to your server. In other words, responses are
cached based on request URL, with one cache entry per URL.

This behavior is referred to as "page-level caching." If this strategy meets
your needs, you can enable it very easily:

  # Page-level caching
  class SimpleApp < Padrino::Application
    register Padrino::Cache
    enable :caching

    get '/foo', :cache => true do
      expires 30 # expire cached version at least every 30 seconds
      'Hello world'
    end
  end

You can also cache on a controller-wide basis:

  # Controller-wide caching example
  class SimpleApp < Padrino::Application
    register Padrino::Cache
    enable :caching

    get '/' do
      'Hello world'
    end

    # Requests to routes within '/admin'
    controller '/admin', :cache => true do
      expires 60

      get '/foo' do
        'Url is /admin/foo'
      end

      get '/bar' do
        'Url is /admin/bar'
      end

      post '/baz' do # We cache only GET and HEAD request
        'This will not be cached'
      end
    end
  end

You can also provide a custom <tt>cache_key</tt> in any route:

  class SimpleApp < Padrino::Application
    register Padrino::Cache
    enable :caching

    get '/post/:id', :cache => true do
      @post = Post.find(params[:id])
      cache_key :my_name
    end
  end

In this way you can manually expire cache with CachedApp.cache.delete(:my_name)
for example from the Post model after an update.

If you specify <tt>:cache => true</tt> but do not invoke <tt>expires</tt>,
the response will be cached indefinitely. Most of the time, you will want to
specify the expiry of a cache entry by <tt>expires</tt>. Even a relatively
low value--1 or 2 seconds--can greatly increase application efficiency, especially
when enabled on a very active part of your domain.

== Helpers

When an application registers padrino-cache, it gains access to several helper
methods. These methods are used according to your caching strategy, so they are
explained here likewise--by functionality.

As with all code optimization, you may want to start simply (at "page level"),
and continue if necessary into sub-page (or "fragment level" ) caching. There
is no one way to approach caching, but it's always good to avoid complexity
until you need it. Start at the page level and see if it works for you.

The padrino-cache helpers are made available to your application thusly:

  # Enable caching
  class CachedApp < Padrino::Application
    register Padrino::Cache  # includes helpers
    enable :caching          # turns on caching

    # ... controllers/routes ...
  end

=== Page Caching

As described above in the "Caching Quickstart" section, page caching is very
easy to integrate into your application. To turn it on, simply provide the
<tt>:cache => true</tt> option on either a controller or one of its routes.
By default, cached content is persisted with a "file store"--that is, in a
subdirectory of your application root.

==== <tt>expires( seconds )</tt>

This helper is used within a controller or route to indicate how often cached
<em>page-level</em> content should persist in the cache.

After <tt>seconds</tt> seconds have passed, content previously cached will
be discarded and re-rendered. Code associated with that route will <em>not</em>
be executed; rather, its previous output will be sent to the client with a
200 OK status code.

  # Setting content expiry time
  class CachedApp < Padrino::Application
    register Padrino::Cache  # includes helpers
    enable :caching          # turns on caching

    controller '/blog', :cache => true do
      expires 15

      get '/entries' do
        'just broke up eating twinkies lol'
      end
    end
  end

Note that the "latest" method call to <tt>expires</tt> determines its value: if
called within a route, as opposed to a controller definition, the route's
value will be assumed.

=== Fragment Caching

Whereas page-level caching, described in the first section of this document, works by
grabbing the entire output of a route, fragment caching gives the developer fine-grained
control of what gets cached. This type of caching occurs at whatever level you choose.

Possible uses for fragment caching might include:

* a 'feed' of some items on a page
* output fetched (by proxy) from an API on a third-party site
* parts of your page which are largely static/do not need re-rendering every request
* any output which is expensive to render

==== <tt>cache( key, opts, &block )</tt>

This helper is used anywhere in your application you would like to associate a fragment
to be cached. It can be used in within a route:

  # Caching a fragment
  class MyTweets < Padrino::Application
    register Padrino::Cache  # includes helpers
    enable :caching          # turns on caching

    controller '/tweets' do
      get :feed, :map => '/:username' do
        username = params[:username]

        @feed = cache( "feed_for_#{username}", :expires => 3 ) do
          @tweets = Tweet.all( :username => username )
          render 'partials/feedcontent'
        end

        # Below outputs @feed somewhere in its markup
        render 'feeds/show'
      end
    end
  end

This example adds a key to the cache of format <tt>feed_for_#{username}</tt> which
contains the contents of that user's feed. Any subsequent action within the next 3 seconds
will fetch the pre-rendered version of <tt>feed_for_#{username}</tt> from the cache
instead of re-rendering it. The rest of the page code will, however, be re-executed.

Note that any other action will reference the same content if it uses the same key:

  # Multiple routes sharing the same cached fragment
  class MyTweets < Padrino::Application
    register Padrino::Cache  # includes helpers
    enable :caching          # turns on caching

    controller :tweets do
      get :feed, :map => '/:username' do
        username = params[:username]

        @feed = cache( "feed_for_#{username}", :expires => 3 ) do
          @tweets = Tweet.all( :username => username )
          render 'partials/feedcontent'
        end

        # Below outputs @feed somewhere in its markup
        render 'feeds/show'
      end

      get :mobile_feed, :map => '/:username.iphone' do
        username = params[:username]

        @feed = cache( "feed_for_#{username}", :expires => 3 ) do
          @tweets = Tweet.all( :username => username )
          render 'partials/feedcontent'
        end

        render 'feeds/show.iphone'
      end
    end
  end

The <tt>opts</tt> argument is actually passed to the underlying store. The stores support the <tt>:expires</tt> option out of the box or
are enhanced by Moneta to support it.

Finally, to DRY up things a bit, we might do:

  # Multiple routes sharing the same cached fragment
  class MyTweets < Padrino::Application
    register Padrino::Cache  # includes helpers
    enable :caching          # turns on caching

    controller :tweets do
      # This works because all routes in this controller specify :username
      before do
        @feed = cache( "feed_for_#{params[:username]}", :expires => 3 ) do
          @tweets = Tweet.all( :username => params[:username] )
          render 'partials/feedcontent'
        end
      end

      get :feed, :map => '/:username' do
        render 'feeds/show'
      end

      get :mobile_feed, :map => '/:username.iphone' do
        render 'feeds/show.iphone'
      end
    end
  end

Of course, this example assumes the markup generated by rendering
<tt>partials/feedcontent</tt> would be suitable for both feed formats. This may or
may not be the case in your application, but the principle applies: fragments
are shared between all code which accesses the cache using the same key.

== Caching Store

You can set a global caching option or a per app caching options.

=== Global Caching Options

  Padrino.cache = Padrino::Cache.new(:LRUHash) # default choice
  Padrino.cache = Padrino::Cache.new(:File, :dir => Padrino.root('tmp', app_name.to_s, 'cache')) # Keeps cached values in file
  Padrino.cache = Padrino::Cache.new(:Memcached) # Uses default server at localhost
  Padrino.cache = Padrino::Cache.new(:Memcached, :server => '127.0.0.1:11211', :exception_retry_limit => 1)
  Padrino.cache = Padrino::Cache.new(:Memcached, :backend => memcached_or_dalli_instance)
  Padrino.cache = Padrino::Cache.new(:Redis) # Uses default server at localhost
  Padrino.cache = Padrino::Cache.new(:Redis, :host => '127.0.0.1', :port => 6379, :db => 0)
  Padrino.cache = Padrino::Cache.new(:Redis, :backend => redis_instance)
  Padrino.cache = Padrino::Cache.new(:Mongo) # Uses default server at localhost
  Padrino.cache = Padrino::Cache.new(:Mongo, :backend => mongo_client_instance)

You can manage your cache from anywhere in your app:

  Padrino.cache['val'] = 'test'
  Padrino.cache['val'] # => 'test'
  Padrino.cache.delete('val')
  Padrino.cache.clear

The Padrino cache constructor `Padrino::Cache.new` calls `Moneta.new` to create a cache instance. Please refer to the [Moneta documentation](http://rubydoc.info/gems/moneta) if you
have special requirements, for example if you want to configure the marshalling mechanism or use a more exotic backend.

=== Application Caching Options

  set :cache, Padrino::Cache.new(:LRUHash)
  set :cache, Padrino::Cache.new(:Memcached)
  set :cache, Padrino::Cache.new(:Redis)
  set :cache, Padrino::Cache.new(:File, :dir => Padrino.root('tmp', app_name.to_s, 'cache')) # default choice

You can manage your cache from anywhere in your app:

  MyApp.cache['val'] = 'test'
  MyApp.cache['val'] # => 'test'
  MyApp.cache.delete('val')
  MyApp.cache.clear

== Expiring Cached Content

In certain circumstances, cached content becomes stale. The  <tt>expire</tt>
helper removes content associated with a key or keys, which your app is then
free to re-generate.

=== <tt>expire( *key )</tt>

==== Fragment-level expiration

Using the example above of a tweet server, let's suppose our users have a
tendency to post things they quickly regret. When we query our database
for new tweets, let's check to see if any have been deleted. If so, we'll
do our user a favor and instantly re-render the feed.

  # Expiring fragment-level cached content
  class MyTweets < Padrino::Application
    register Padrino::Cache # includes helpers
    enable :caching         # turns on caching
    enable :session         # we'll use this to store last time visited

    COMPANY_FOUNDING = Time.utc( 2010, "April" )

    controller :tweets do
      get :feed, :map => '/:username' do
        last_visit = session[:last_visit] || params[:since] || COMPANY_FOUNDING

        username = params[:username]
        @tweets = Tweet.since( last_visit, :username => username ).limit( 100 )

        expire( "feed since #{last_visit}" ) if @tweets.any? { |t| t.deleted_since?( last_visit ) }

        session[:last_visit] = Time.now
        @feed = cache( "feed since #{last_visit}", :expires => 60 ) do
          @tweets = @tweets.find_all { |t| !t.deleted? }
          render 'partials/feedcontent'
        end

        render 'feeds/show'
      end
    end
  end

Normally, this example will only re-cache feed content every 60 seconds,
but it will do so immediately if any tweets have been deleted.

==== Page-level expiration

Page-level expiration works exactly like the example above--by using
<tt>expire</tt> in your controller. The key is typically <tt>env['PATH_INFO']</tt>.

== Copyright

Copyright (c) 2011-2015 Padrino. See LICENSE for details.