benjaminjackson-sinatra-cache 0.3.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +16 -0
- data/LICENSE +20 -0
- data/README.rdoc +417 -0
- data/Rakefile +89 -0
- data/VERSION +1 -0
- data/lib/sinatra/cache.rb +13 -0
- data/lib/sinatra/cache/helpers.rb +667 -0
- data/lib/sinatra/templates.rb +64 -0
- data/sinatra-cache.gemspec +73 -0
- data/spec/fixtures/apps/base/views/css.sass +2 -0
- data/spec/fixtures/apps/base/views/fragments.erb +11 -0
- data/spec/fixtures/apps/base/views/fragments_shared.erb +11 -0
- data/spec/fixtures/apps/base/views/index.erb +1 -0
- data/spec/fixtures/apps/base/views/layout.erb +9 -0
- data/spec/fixtures/apps/base/views/params.erb +3 -0
- data/spec/fixtures/apps/partials/views/home/index.erb +4 -0
- data/spec/fixtures/apps/partials/views/layout.erb +11 -0
- data/spec/fixtures/apps/partials/views/shared/_header.erb +3 -0
- data/spec/sinatra/cache_spec.rb +967 -0
- data/spec/spec_helper.rb +66 -0
- metadata +114 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 kematzy
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,417 @@
|
|
1
|
+
= Sinatra::Cache
|
2
|
+
|
3
|
+
A Sinatra Extension that makes Page and Fragment Caching easy.
|
4
|
+
|
5
|
+
|
6
|
+
== IMPORTANT INFORMATION
|
7
|
+
|
8
|
+
<b>This is a completely rewritten extension that basically breaks all previous versions of it.</b>
|
9
|
+
|
10
|
+
So use with care! You have been warned ;-)
|
11
|
+
|
12
|
+
----
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
With that said, on to the real stuff.
|
17
|
+
|
18
|
+
|
19
|
+
== Installation
|
20
|
+
|
21
|
+
# Add RubyGems.org (former Gemcutter) to your RubyGems sources
|
22
|
+
$ gem sources -a http://rubygems.org
|
23
|
+
|
24
|
+
$ (sudo)? gem install sinatra-cache
|
25
|
+
|
26
|
+
== Dependencies
|
27
|
+
|
28
|
+
This Gem depends upon the following:
|
29
|
+
|
30
|
+
=== Runtime:
|
31
|
+
|
32
|
+
* sinatra ( >= 1.0.a )
|
33
|
+
* sinatra-outputbuffer[http://github.com/kematzy/sinatra-outputbuffer] (>= 0.1.0)
|
34
|
+
|
35
|
+
Optionals:
|
36
|
+
|
37
|
+
* sinatra-settings[http://github.com/kematzy/sinatra-settings] (>= 0.1.1) # to view default settings in a browser display.
|
38
|
+
|
39
|
+
=== Development & Tests:
|
40
|
+
|
41
|
+
* sinatra-tests (>= 0.1.6)
|
42
|
+
* rspec (>= 1.3.0 )
|
43
|
+
* rack-test (>= 0.5.3)
|
44
|
+
* rspec_hpricot_matchers (>= 0.1.0)
|
45
|
+
* fileutils
|
46
|
+
* sass
|
47
|
+
* ostruct
|
48
|
+
* yaml
|
49
|
+
* json
|
50
|
+
|
51
|
+
|
52
|
+
== Getting Started
|
53
|
+
|
54
|
+
To start caching your app's ouput, just require and register
|
55
|
+
the extension in your sub-classed Sinatra app:
|
56
|
+
|
57
|
+
require 'sinatra/cache'
|
58
|
+
|
59
|
+
class YourApp < Sinatra::Base
|
60
|
+
|
61
|
+
# NB! you need to set the root of the app first
|
62
|
+
set :root, '/path/2/the/root/of/your/app'
|
63
|
+
|
64
|
+
register(Sinatra::Cache)
|
65
|
+
|
66
|
+
set :cache_enabled, true # turn it on
|
67
|
+
|
68
|
+
<snip...>
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
In your "classic" Sinatra app, you just require the extension and set some key settings, like this:
|
74
|
+
|
75
|
+
require 'rubygems'
|
76
|
+
require 'sinatra'
|
77
|
+
require 'sinatra/cache'
|
78
|
+
|
79
|
+
# NB! you need to set the root of the app first
|
80
|
+
set :root, '/path/2/the/root/of/your/app'
|
81
|
+
set :public, '/path/2/public'
|
82
|
+
|
83
|
+
set :cache_enabled, true # turn it on
|
84
|
+
|
85
|
+
<snip...>
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
That's more or less it.
|
90
|
+
|
91
|
+
You should now be caching your output by default, in <tt>:production</tt> mode, as long as you use
|
92
|
+
one of Sinatra's render methods:
|
93
|
+
|
94
|
+
erb(), erubis(), haml(), sass(), builder(), etc..
|
95
|
+
|
96
|
+
...or any render method that uses <tt>Sinatra::Templates#render()</tt> as its base.
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
== Configuration Settings
|
101
|
+
|
102
|
+
The default settings should help you get moving quickly, and are fairly common sense based.
|
103
|
+
|
104
|
+
|
105
|
+
==== <tt>:cache_enabled</tt>
|
106
|
+
|
107
|
+
This setting toggles the cache functionality On / Off.
|
108
|
+
Default is: <tt>false</tt>
|
109
|
+
|
110
|
+
|
111
|
+
==== <tt>:cache_environment</tt>
|
112
|
+
|
113
|
+
Sets the environment during which the cache functionality is active.
|
114
|
+
Default is: <tt>:production</tt>
|
115
|
+
|
116
|
+
|
117
|
+
==== <tt>:cache_page_extension</tt>+
|
118
|
+
|
119
|
+
Sets the default file extension for cached files.
|
120
|
+
Default is: <tt>.html</tt>
|
121
|
+
|
122
|
+
|
123
|
+
==== <tt>:cache_output_dir</tt>
|
124
|
+
|
125
|
+
Sets cache directory where the cached files are stored.
|
126
|
+
Default is: == "/path/2/your/app/public"
|
127
|
+
|
128
|
+
Although you can set it to the more ideal '<tt>..public/system/cache/</tt>'
|
129
|
+
if you can get that to work with your webserver setup.
|
130
|
+
|
131
|
+
|
132
|
+
==== <tt>:cache_fragments_output_dir</tt>
|
133
|
+
|
134
|
+
Sets the directory where cached fragments are stored.
|
135
|
+
Default is the '../tmp/cache_fragments/' directory at the root of your app.
|
136
|
+
|
137
|
+
This is for security reasons since you don't really want your cached fragments publically available.
|
138
|
+
|
139
|
+
|
140
|
+
==== <tt>:cache_fragments_wrap_with_html_comments</tt>
|
141
|
+
|
142
|
+
This setting toggles the wrapping of cached fragments in HTML comments. (see below)
|
143
|
+
Default is: <tt>true</tt>
|
144
|
+
|
145
|
+
|
146
|
+
==== <tt>:cache_logging</tt>
|
147
|
+
|
148
|
+
This setting toggles the logging of various cache calls. If the app has access to the <tt>#logger</tt> method,
|
149
|
+
curtesy of Sinatra::Logger[http://github.com/kematzy/sinatra-logger] then it will log there, otherwise logging
|
150
|
+
is silent.
|
151
|
+
|
152
|
+
Default is: <tt>true</tt>
|
153
|
+
|
154
|
+
|
155
|
+
==== <tt>:cache_logging_level</tt>
|
156
|
+
|
157
|
+
Sets the level at which the cache logger should log it's messages.
|
158
|
+
Default is: <tt>:info</tt>
|
159
|
+
|
160
|
+
Available options are: [:fatal, :error, :warn, :info, :debug]
|
161
|
+
|
162
|
+
|
163
|
+
== Basic Page Caching
|
164
|
+
|
165
|
+
By default caching only happens in <tt>:production</tt> mode, and via the Sinatra render methods, erb(), etc,
|
166
|
+
|
167
|
+
So asuming we have the following setup (continued from above)
|
168
|
+
|
169
|
+
|
170
|
+
class YourApp
|
171
|
+
|
172
|
+
<snip...>
|
173
|
+
|
174
|
+
set :cache_output_dir, "/full/path/2/app/root/public/system/cache"
|
175
|
+
|
176
|
+
<snip...>
|
177
|
+
|
178
|
+
get('/') { erb(:index) } # => cached as '../index.html'
|
179
|
+
|
180
|
+
get('/contact') { erb(:contact) } # => cached as '../contact.html'
|
181
|
+
|
182
|
+
# NB! the trailing slash on the URL
|
183
|
+
get('/about/') { erb(:about) } # => cached as '../about/index.html'
|
184
|
+
|
185
|
+
get('/feed.rss') { builder(:feed) } # => cached as '../feed.rss'
|
186
|
+
# NB! uses the extension of the passed URL,
|
187
|
+
# but DOES NOT ensure the format of the content based on the extension provided.
|
188
|
+
|
189
|
+
# complex URL with multiple possible params
|
190
|
+
get %r{/articles/?([\s\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?} do
|
191
|
+
erb(:articles)
|
192
|
+
end
|
193
|
+
# with the '/articles/a/b/c => cached as ../articles/a/b/c.html
|
194
|
+
|
195
|
+
# NB! the trailing slash on the URL
|
196
|
+
# with the '/articles/a/b/c/ => cached as ../articles/a/b/c/index.html
|
197
|
+
|
198
|
+
# CSS caching via Sass # => cached as '.../css/screen.css'
|
199
|
+
get '/css/screen.css' do
|
200
|
+
content_type 'text/css'
|
201
|
+
sass(:'css/screen')
|
202
|
+
end
|
203
|
+
|
204
|
+
# to turn off caching on certain pages.
|
205
|
+
get('/dont/cache/this/page') { erb(:aview, :cache => false) } # => is NOT cached
|
206
|
+
|
207
|
+
|
208
|
+
# NB! any query string params - [ /?page=X&id=y ] - are stripped off and TOTALLY IGNORED
|
209
|
+
# during the caching process.
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
OK, that's about all you need to know about basic Page Caching right there. Read the above example
|
214
|
+
carefully until you understand all the variations.
|
215
|
+
|
216
|
+
|
217
|
+
== Fragment Caching
|
218
|
+
|
219
|
+
If you just need to cache a fragment of a page, then you'd do as follows:
|
220
|
+
|
221
|
+
class YourApp
|
222
|
+
|
223
|
+
set :cache_fragments_output_dir, "/full/path/2/fragments/store/location"
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
Then in your views / layouts add the following:
|
228
|
+
|
229
|
+
<% cache_fragment(:name_of_fragment) do %>
|
230
|
+
# do something worth caching
|
231
|
+
<% end %>
|
232
|
+
|
233
|
+
|
234
|
+
Each fragment is stored in the same directory structure as your request
|
235
|
+
so, if you have a request like this:
|
236
|
+
|
237
|
+
get '/articles/2010/02' ...
|
238
|
+
|
239
|
+
...the cached fragment will be stored as:
|
240
|
+
|
241
|
+
../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
|
242
|
+
|
243
|
+
This enables you to use similar names for your fragments or have
|
244
|
+
multiple URLs use the same view / layout.
|
245
|
+
|
246
|
+
|
247
|
+
=== An important limitation
|
248
|
+
|
249
|
+
The fragment caching is dependent upon the final URL, so in the case of
|
250
|
+
a blog, where each article uses the same view, but through different URLs,
|
251
|
+
each of the articles would cache it's own fragment, which is ineffecient.
|
252
|
+
|
253
|
+
To sort-of deal with this limitation I have temporarily added a very hackish
|
254
|
+
'fix' through adding a 2nd parameter (see example below), which will remove the
|
255
|
+
last part of the URL and use the rest of the URL as the stored fragment path.
|
256
|
+
|
257
|
+
So given the URL:
|
258
|
+
|
259
|
+
get '/articles/2010/02/fragment-caching-with-sinatra-cache' ...
|
260
|
+
|
261
|
+
and the following <tt>#cache_fragment</tt> declaration in your view
|
262
|
+
|
263
|
+
<% cache_fragment(:name_of_fragment, :shared) do %>
|
264
|
+
# do something worth caching
|
265
|
+
<% end %>
|
266
|
+
|
267
|
+
...the cached fragment would be stored as:
|
268
|
+
|
269
|
+
../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
|
270
|
+
|
271
|
+
Any other URLs with the same URL root, like...
|
272
|
+
|
273
|
+
get '/articles/2010/02/writing-sinatra-extensions' ...
|
274
|
+
|
275
|
+
... would use the same cached fragment.
|
276
|
+
|
277
|
+
|
278
|
+
<b>NB!</b> currently only supports one level, but Your fork might fix that ;-)
|
279
|
+
|
280
|
+
|
281
|
+
== Cache Expiration
|
282
|
+
|
283
|
+
<b>Under development, and not entirely final.</b> See Todo's below for more info.
|
284
|
+
|
285
|
+
|
286
|
+
To expire a cached item - file or fragment you use the :cache_expire() method.
|
287
|
+
|
288
|
+
|
289
|
+
cache_expire('/contact') => expires ../contact.html
|
290
|
+
|
291
|
+
|
292
|
+
# NB! notice the trailing slash
|
293
|
+
cache_expire('/contact/') => expires ../contact/index.html
|
294
|
+
|
295
|
+
|
296
|
+
cache_expire('/feed.rss') => expires ../feed.rss
|
297
|
+
|
298
|
+
|
299
|
+
To expire a cached fragment:
|
300
|
+
|
301
|
+
cache_expire('/some/path', :fragment => :name_of_fragment )
|
302
|
+
|
303
|
+
=> expires ../some/path/:name_of_fragment.html
|
304
|
+
|
305
|
+
|
306
|
+
|
307
|
+
== A few important points to consider
|
308
|
+
|
309
|
+
|
310
|
+
=== The DANGERS of URL query string params
|
311
|
+
|
312
|
+
By default the caching ignores the query string params, but that's not the only problem with query params.
|
313
|
+
|
314
|
+
Let's say you have a URL like this:
|
315
|
+
|
316
|
+
/products/?product_id=111
|
317
|
+
|
318
|
+
and then inside that template [ .../views/products.erb ], you use the <tt>params[:product_id]</tt>
|
319
|
+
param passed in for some purpose.
|
320
|
+
|
321
|
+
<ul>
|
322
|
+
<li>Product ID: <%= params[:product_id] %></li> # => 111
|
323
|
+
...
|
324
|
+
</ul>
|
325
|
+
|
326
|
+
If you cache this URL, then the cached file [ ../cache/products.html ] will be stored with that
|
327
|
+
value embedded. Obviously not ideal for any other similar URLs with different <tt>product_id</tt>'s
|
328
|
+
|
329
|
+
To overcome this issue, use either of these two methods.
|
330
|
+
|
331
|
+
# in your_app.rb
|
332
|
+
|
333
|
+
# turning off caching on this page
|
334
|
+
|
335
|
+
get '/products/' do
|
336
|
+
...
|
337
|
+
erb(:products, :cache => false)
|
338
|
+
end
|
339
|
+
|
340
|
+
# or
|
341
|
+
|
342
|
+
# rework the URLs to something like '/products/111 '
|
343
|
+
|
344
|
+
get '/products/:product_id' do
|
345
|
+
...
|
346
|
+
erb(:products)
|
347
|
+
end
|
348
|
+
|
349
|
+
|
350
|
+
|
351
|
+
Thats's about all the information you need to know.
|
352
|
+
|
353
|
+
|
354
|
+
== RTFM
|
355
|
+
|
356
|
+
If the above is not clear enough, please check the Specs for a better understanding.
|
357
|
+
|
358
|
+
|
359
|
+
== Errors / Bugs
|
360
|
+
|
361
|
+
If something is not behaving intuitively, it is a bug, and should be reported.
|
362
|
+
Report it here: http://github.com/kematzy/sinatra-cache/issues
|
363
|
+
|
364
|
+
|
365
|
+
== TODOs
|
366
|
+
|
367
|
+
* Improve the fragment caching functionality
|
368
|
+
|
369
|
+
* Decide on how to handle site-wide shared fragments.
|
370
|
+
|
371
|
+
* Make the shared fragments more dynamic or usable
|
372
|
+
|
373
|
+
* Work out how to use the <tt>cache_expire()</tt> functionality in a logical way.
|
374
|
+
|
375
|
+
* Work out and include instructions on how to use a '../public/custom/cache/dir' with Passenger.
|
376
|
+
|
377
|
+
* Enable time-based / date-based cache expiry and regeneration of the cached-pages. [ht oakleafs]
|
378
|
+
|
379
|
+
* Enable .gz version of the cached file, further reducing the processing on the server. [ht oakleafs]
|
380
|
+
It would be killer to have <b>an extra .gz file next to the cached file</b>. That way, in Apache, you set it up like that:
|
381
|
+
|
382
|
+
RewriteCond %{HTTP:Accept-Encoding} gzip
|
383
|
+
RewriteCond %{REQUEST_FILENAME}.gz$ -f
|
384
|
+
RewriteRule ^(.*)$ $1.gz [L,QSA]
|
385
|
+
|
386
|
+
And it should serve the compressed file if available.
|
387
|
+
|
388
|
+
* Write more tests to ensure everything is very solid.
|
389
|
+
|
390
|
+
* Any other improvements you or I can think of.
|
391
|
+
|
392
|
+
|
393
|
+
== Note on Patches/Pull Requests
|
394
|
+
|
395
|
+
* Fork the project.
|
396
|
+
* Make your feature addition or bug fix.
|
397
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
398
|
+
* Commit, do not mess with rakefile, version, or history.
|
399
|
+
* (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)
|
400
|
+
* Send me a pull request. Bonus points for topic branches.
|
401
|
+
|
402
|
+
== Copyright
|
403
|
+
|
404
|
+
Copyright (c) 2009-2010 kematzy. Released under the MIT License.
|
405
|
+
|
406
|
+
See LICENSE for details.
|
407
|
+
|
408
|
+
=== Credits
|
409
|
+
|
410
|
+
A big <b>Thank You!</b> goes to rtomayko[http://github/rtomayko], blakemizerany[http://github.com/blakemizerany/]
|
411
|
+
and others working on the Sinatra framework.
|
412
|
+
|
413
|
+
=== Inspirations
|
414
|
+
|
415
|
+
Inspired by code from Rails[http://rubyonrails.com/] & Merb[http://merbivore.com/]
|
416
|
+
and other sources
|
417
|
+
|