¶ ↑
Sinatra::CacheA Sinatra Extension that makes Page and Fragment Caching easy.
¶ ↑
IMPORTANT INFORMATIONThis is a completely rewritten extension that basically breaks all previous versions of it.
So use with care! You have been warned ;-)
With that said, on to the real stuff.
¶ ↑
Installation# Add RubyGems.org (former Gemcutter) to your RubyGems sources $ gem sources -a http://rubygems.org $ (sudo)? gem install sinatra-cache
¶ ↑
DependenciesThis Gem depends upon the following:
¶ ↑
Runtime:-
sinatra ( >= 1.0.a )
-
sinatra-outputbuffer (>= 0.1.0)
Optionals:
-
sinatra-settings (>= 0.1.1) # to view default settings in a browser display.
¶ ↑
Development & Tests:-
sinatra-tests (>= 0.1.6)
-
rspec (>= 1.3.0 )
-
rack-test (>= 0.5.3)
-
rspec_hpricot_matchers (>= 0.1.0)
-
fileutils
-
sass
-
ostruct
-
yaml
-
json
¶ ↑
Getting StartedTo start caching your app’s ouput, just require and register the extension in your sub-classed Sinatra app:
require 'sinatra/cache' class YourApp < Sinatra::Base # NB! you need to set the root of the app first set :root, '/path/2/the/root/of/your/app' register(Sinatra::Cache) set :cache_enabled, true # turn it on <snip...> end
In your “classic” Sinatra app, you just require the extension and set some key settings, like this:
require 'rubygems' require 'sinatra' require 'sinatra/cache' # NB! you need to set the root of the app first set :root, '/path/2/the/root/of/your/app' set :public, '/path/2/public' set :cache_enabled, true # turn it on <snip...>
That’s more or less it.
You should now be caching your output by default, in :production
mode, as long as you use one of Sinatra’s render methods:
erb(), erubis(), haml(), sass(), builder(), etc..
…or any render method that uses Sinatra::Templates#render()
as its base.
¶ ↑
Configuration SettingsThe default settings should help you get moving quickly, and are fairly common sense based.
:cache_enabled
¶ ↑
This setting toggles the cache functionality On / Off. Default is: false
:cache_environment
¶ ↑
Sets the environment during which the cache functionality is active. Default is: :production
:cache_page_extension
+ ¶ ↑
Sets the default file extension for cached files. Default is: .html
:cache_output_dir
¶ ↑
Sets cache directory where the cached files are stored. Default is: == “/path/2/your/app/public”
Although you can set it to the more ideal ‘..public/system/cache/
’ if you can get that to work with your webserver setup.
:cache_fragments_output_dir
¶ ↑
Sets the directory where cached fragments are stored. Default is the ‘../tmp/cache_fragments/’ directory at the root of your app.
This is for security reasons since you don’t really want your cached fragments publically available.
:cache_fragments_wrap_with_html_comments
¶ ↑
This setting toggles the wrapping of cached fragments in HTML comments. (see below) Default is: true
:cache_logging
¶ ↑
This setting toggles the logging of various cache calls. If the app has access to the #logger
method, curtesy of Sinatra::Logger then it will log there, otherwise logging is silent.
Default is: true
:cache_logging_level
¶ ↑
Sets the level at which the cache logger should log it’s messages. Default is: :info
Available options are: [:fatal, :error, :warn, :info, :debug]
¶ ↑
Basic Page CachingBy default caching only happens in :production
mode, and via the Sinatra render methods, erb(), etc,
So asuming we have the following setup (continued from above)
class YourApp <snip...> set :cache_output_dir, "/full/path/2/app/root/public/system/cache" <snip...> get('/') { erb(:index) } # => cached as '../index.html' get('/contact') { erb(:contact) } # => cached as '../contact.html' # NB! the trailing slash on the URL get('/about/') { erb(:about) } # => cached as '../about/index.html' get('/feed.rss') { builder(:feed) } # => cached as '../feed.rss' # NB! uses the extension of the passed URL, # but DOES NOT ensure the format of the content based on the extension provided. # complex URL with multiple possible params get %r{/articles/?([\s\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?} do erb(:articles) end # with the '/articles/a/b/c => cached as ../articles/a/b/c.html # NB! the trailing slash on the URL # with the '/articles/a/b/c/ => cached as ../articles/a/b/c/index.html # CSS caching via Sass # => cached as '.../css/screen.css' get '/css/screen.css' do content_type 'text/css' sass(:'css/screen') end # to turn off caching on certain pages. get('/dont/cache/this/page') { erb(:aview, :cache => false) } # => is NOT cached # NB! any query string params - [ /?page=X&id=y ] - are stripped off and TOTALLY IGNORED # during the caching process. end
OK, that’s about all you need to know about basic Page Caching right there. Read the above example carefully until you understand all the variations.
¶ ↑
Fragment CachingIf you just need to cache a fragment of a page, then you’d do as follows:
class YourApp set :cache_fragments_output_dir, "/full/path/2/fragments/store/location" end
Then in your views / layouts add the following:
<% cache_fragment(:name_of_fragment) do %> # do something worth caching <% end %>
Each fragment is stored in the same directory structure as your request so, if you have a request like this:
get '/articles/2010/02' ...
…the cached fragment will be stored as:
../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
This enables you to use similar names for your fragments or have multiple URLs use the same view / layout.
¶ ↑
An important limitationThe fragment caching is dependent upon the final URL, so in the case of a blog, where each article uses the same view, but through different URLs, each of the articles would cache it’s own fragment, which is ineffecient.
To sort-of deal with this limitation I have temporarily added a very hackish ‘fix’ through adding a 2nd parameter (see example below), which will remove the last part of the URL and use the rest of the URL as the stored fragment path.
So given the URL:
get '/articles/2010/02/fragment-caching-with-sinatra-cache' ...
and the following #cache_fragment
declaration in your view
<% cache_fragment(:name_of_fragment, :shared) do %> # do something worth caching <% end %>
…the cached fragment would be stored as:
../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
Any other URLs with the same URL root, like…
get '/articles/2010/02/writing-sinatra-extensions' ...
… would use the same cached fragment.
NB! currently only supports one level, but Your fork might fix that ;-)
¶ ↑
Cache ExpirationUnder development, and not entirely final. See Todo’s below for more info.
To expire a cached item - file or fragment you use the :cache_expire() method.
cache_expire('/contact') => expires ../contact.html # NB! notice the trailing slash cache_expire('/contact/') => expires ../contact/index.html cache_expire('/feed.rss') => expires ../feed.rss
To expire a cached fragment:
cache_expire('/some/path', :fragment => :name_of_fragment ) => expires ../some/path/:name_of_fragment.html
¶ ↑
A few important points to consider¶ ↑
The DANGERS of URL query string paramsBy default the caching ignores the query string params, but that’s not the only problem with query params.
Let’s say you have a URL like this:
/products/?product_id=111
and then inside that template [ …/views/products.erb ], you use the params[:product_id]
param passed in for some purpose.
<ul> <li>Product ID: <%= params[:product_id] %></li> # => 111 ... </ul>
If you cache this URL, then the cached file [ ../cache/products.html ] will be stored with that value embedded. Obviously not ideal for any other similar URLs with different product_id
‘s
To overcome this issue, use either of these two methods.
# in your_app.rb # turning off caching on this page get '/products/' do ... erb(:products, :cache => false) end # or # rework the URLs to something like '/products/111 ' get '/products/:product_id' do ... erb(:products) end
Thats’s about all the information you need to know.
¶ ↑
RTFMIf the above is not clear enough, please check the Specs for a better understanding.
¶ ↑
Errors / BugsIf something is not behaving intuitively, it is a bug, and should be reported. Report it here: github.com/kematzy/sinatra-cache/issues
¶ ↑
TODOs-
Improve the fragment caching functionality
-
Decide on how to handle site-wide shared fragments.
-
Make the shared fragments more dynamic or usable
-
-
Work out how to use the
cache_expire()
functionality in a logical way. -
Work out and include instructions on how to use a ‘../public/custom/cache/dir’ with Passenger.
-
Enable time-based / date-based cache expiry and regeneration of the cached-pages. [ht oakleafs]
-
Enable .gz version of the cached file, further reducing the processing on the server. [ht oakleafs] It would be killer to have an extra .gz file next to the cached file. That way, in Apache, you set it up like that:
RewriteCond %{HTTP:Accept-Encoding} gzip RewriteCond %{REQUEST_FILENAME}.gz$ -f RewriteRule ^(.*)$ $1.gz [L,QSA]
And it should serve the compressed file if available.
-
Write more tests to ensure everything is very solid.
-
Any other improvements you or I can think of.
¶ ↑
Note on Patches/Pull Requests-
Fork the project.
-
Make your feature addition or bug fix.
-
Add tests for it. This is important so I don’t break it in a future version unintentionally.
-
Commit, do not mess with rakefile, version, or history.
-
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
-
-
Send me a pull request. Bonus points for topic branches.
¶ ↑
CopyrightCopyright © 2009-2010 kematzy. Released under the MIT License.
See LICENSE for details.
¶ ↑
CreditsA big Thank You! goes to rtomayko, blakemizerany and others working on the Sinatra framework.