• Stars
    star
    161
  • Rank 233,470 (Top 5 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 16 years ago
  • Updated over 12 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Sinatra Extension that makes Page and Fragment Caching easy

Sinatra::Cache

A Sinatra Extension that makes Page and Fragment Caching easy.

IMPORTANT INFORMATION

This 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

Dependencies

This Gem depends upon the following:

Runtime:

Optionals:

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 Started

To 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 Settings

The 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 Caching

By 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 Caching

If 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 limitation

The 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 Expiration

Under 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 params

By 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.

RTFM

If the above is not clear enough, please check the Specs for a better understanding.

Errors / Bugs

If 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.

Copyright © 2009-2010 kematzy. Released under the MIT License.

See LICENSE for details.

Credits

A big Thank You! goes to rtomayko, blakemizerany and others working on the Sinatra framework.

Inspirations

Inspired by code from Rails & Merb and other sources

More Repositories

1

sinatra-logger

A Sinatra Extension that makes logging easy
Ruby
34
star
2

storybook-svelte-kit-vite-app

A working example of Storybook, with Vite, Svelte Kit and Tailwind CSS
CSS
24
star
3

sinatra-outputbuffer

A Sinatra Extension that makes content output buffering easy.
Ruby
17
star
4

sinatra-settings

A Sinatra Extension that shows your app's settings and other debug information
Ruby
15
star
5

conky

My personal version of Conky config
12
star
6

roda-i18n

Internationalisation (i18n) and localisation support for Roda apps [http://roda.jeremyevans.net/].
Ruby
12
star
7

sequel-audited

A Sequel plugin that logs all changes made to a model, including who created, updated and destroyed the record, and what was changed and when the change was made
Ruby
8
star
8

php-vs-sinatra-memory-usage-app

Two almost identical apps in two frameworks - Ruby (Sinatra) vs PHP (Limonade).
PHP
8
star
9

ionic4-vue-app-sidemenu

A sample Vue.js & Ionic 4 app mostly replicating the default Angular 'sidemenu' app
Vue
5
star
10

sinatra-tests

A Sinatra extension and repository of common Test/RSpec helpers when testing your Sinatra Apps
Ruby
5
star
11

sinatra-ie6nomore

A simple Sinatra Extension in support of the "IE6 No More" [www.ie6nomore.com] campaign to rid the world of the nasty bug ridden monstrosity called IE6.
Ruby
4
star
12

dm-forms

Framework agnostic DataMapper model form generation
Ruby
4
star
13

minitest-sequel

Minitest assertions to speed-up development and testing of Ruby Sequel database setups.
Ruby
4
star
14

minitest-have_tag

Adds Minitest assertions to test for the existence of HTML tags, including contents, within a provided string.
Ruby
4
star
15

sinatra-dm

Sinatra Extension for working with DataMapper (another Sinatra-Sequel Rip-off)
Ruby
3
star
16

dm-is-published

DataMapper plugin that provides basic published status support for models
Ruby
3
star
17

dm_usage_recipes

A collection of DataMapper usage recipes, combined with RSpec tests. Created to help understand DM and all it's features
Ruby
3
star
18

dm-is-select

A DataMapper plugin that makes getting the <select> options from a Model easier.
Ruby
3
star
19

sinatra-tags

A Sinatra Extension that provides easy creation of flexible HTML tags.
Ruby
3
star
20

sinatra-app

A basic template for website development using Sinatra, DataMapper and others.
3
star
21

sinatra-forms

A Sinatra Extension that makes working with forms easy.
Ruby
3
star
22

alt-tmbundles

Alternative TM Bundles, are copies of the default .tmbundles, but with their uuid's changed, so that you can test these out without losing the defaults
Ruby
3
star
23

af-ruby-rack-sinatra-modular

AppFog Ruby / Rack / Sinatra Modular app setup example
Ruby
2
star
24

sitegen

2
star
25

dm-is-markup

A DataMapper plugin that provides easy-to-use rendered attributes
Ruby
2
star
26

minitest-assert_errors

Adds Minitest assertions to test for errors raised or not raised by Minitest itself
Ruby
2
star
27

vuejs-notes

A personal collection of VueJS, Vuex & Firebase related information built with VuePress
Shell
2
star
28

dm-actionstamps

A DataMapper plugin that works similar to the dm-timestamps in that it 'automagically' adds/updates the created_?, updated_? fields of your models.
Ruby
2
star
29

rext

Ruby extensions primarily abstracted from the evolution CMS
Ruby
2
star
30

roda-tags

A Roda Plugin providing easy creation of flexible HTML tags
Ruby
2
star
31

sinatra-scaffold

Scaffolding extension for Sinatra & DataMapper
Ruby
2
star
32

nodejs-express-test-apps

A collection of express test apps in separate branches
1
star
33

cheats.kematzy.com

My personal collection of Cheatsheets
1
star
34

roda-ui_helpers

A collection of Roda plugins with UI helper methods for use in Roda apps or within other Roda plugins
1
star
35

sinatra-blog

A quick example of a Gem based Sinatra App
Ruby
1
star
36

kematzy-tasks

A collection of helpful Rake tasks for improved workflows
Ruby
1
star
37

sapper-seo-trial

Trying to learn how to DRY out the SEO tags in a Sapper app
JavaScript
1
star
38

minitest-rack

Minitest & rack-test convenience assertions/expectations for DRY'er testing of your web apps
Ruby
1
star
39

understanding-sequelize

Various code and tests to help a noob understand how to use Sequelize. (with Mocha & Should.js)
JavaScript
1
star
40

rbext

A collection of Ruby extensions as an alternative to ActiveSupport
Ruby
1
star
41

roda-albums

How to create a Roda plugin with embedded routes
Ruby
1
star
42

sequel-rake-tasks

A collection Rake tasks for use with Ruby's Sequel ORM
1
star