sinatra-cache 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +14 -7
- data/LICENSE +1 -1
- data/README.rdoc +394 -0
- data/Rakefile +66 -23
- data/VERSION +1 -0
- data/lib/sinatra/cache.rb +10 -136
- data/lib/sinatra/cache/helpers.rb +663 -0
- data/lib/sinatra/output.rb +147 -0
- data/lib/sinatra/templates.rb +55 -0
- data/sinatra-cache.gemspec +30 -20
- 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/{test/fixtures → spec/fixtures/apps/base}/views/index.erb +0 -0
- data/spec/fixtures/apps/base/views/layout.erb +9 -0
- data/spec/fixtures/apps/base/views/params.erb +3 -0
- data/spec/sinatra/cache_spec.rb +593 -0
- data/spec/spec_helper.rb +62 -0
- metadata +73 -24
- data/README.md +0 -121
- data/VERSION.yml +0 -4
- data/test/SPECS.rdoc +0 -95
- data/test/cache_test.rb +0 -434
- data/test/fixtures/classic.rb +0 -25
- data/test/fixtures/myapp.rb +0 -36
- data/test/fixtures/myapp_default.rb +0 -37
- data/test/helper.rb +0 -62
data/.gitignore
CHANGED
@@ -1,9 +1,16 @@
|
|
1
|
-
## OS
|
1
|
+
## OS STUFF
|
2
2
|
.DS_Store
|
3
|
+
|
4
|
+
## TM SPECIFIC
|
3
5
|
*.tmproj
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
tmtags
|
7
|
+
|
8
|
+
## PROJECT::GENERAL
|
9
|
+
*.sw?
|
10
|
+
coverage
|
11
|
+
rdoc
|
12
|
+
pkg
|
13
|
+
doc
|
14
|
+
|
15
|
+
## PROJECT::SPECIFIC
|
16
|
+
spec/fixtures/public/*
|
data/LICENSE
CHANGED
data/README.rdoc
ADDED
@@ -0,0 +1,394 @@
|
|
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
|
+
|
34
|
+
|
35
|
+
=== Development & Tests:
|
36
|
+
|
37
|
+
* rspec (>= 1.3.0 )
|
38
|
+
* rack-test (>= 0.5.3)
|
39
|
+
* rspec_hpricot_matchers (>= 0.1.0)
|
40
|
+
* sinatra-tests (>= 0.1.6)
|
41
|
+
* fileutils
|
42
|
+
* sass
|
43
|
+
* ostruct
|
44
|
+
* yaml
|
45
|
+
* json
|
46
|
+
|
47
|
+
|
48
|
+
== Getting Started
|
49
|
+
|
50
|
+
To start caching your app's ouput, just require and register
|
51
|
+
the extension in your sub-classed Sinatra app:
|
52
|
+
|
53
|
+
require 'sinatra/cache'
|
54
|
+
|
55
|
+
class YourApp < Sinatra::Base
|
56
|
+
|
57
|
+
# NB! you need to set the root of the app first
|
58
|
+
set :root, '/path/2/the/root/of/your/app'
|
59
|
+
|
60
|
+
register(Sinatra::Cache)
|
61
|
+
|
62
|
+
set :cache_enabled, true # turn it on
|
63
|
+
|
64
|
+
<snip...>
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
That's more or less it.
|
70
|
+
|
71
|
+
You should now be caching your output by default, in <tt>:production</tt> mode, as long as you use
|
72
|
+
one of Sinatra's render methods:
|
73
|
+
|
74
|
+
erb(), erubis(), haml(), sass(), builder(), etc..
|
75
|
+
|
76
|
+
...or any render method that uses <tt>Sinatra::Templates#render()</tt> as its base.
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
== Configuration Settings
|
81
|
+
|
82
|
+
The default settings should help you get moving quickly, and are fairly common sense based.
|
83
|
+
|
84
|
+
|
85
|
+
==== <tt>:cache_enabled</tt>
|
86
|
+
|
87
|
+
This setting toggles the cache functionality On / Off.
|
88
|
+
Default is: <tt>false</tt>
|
89
|
+
|
90
|
+
|
91
|
+
==== <tt>:cache_environment</tt>
|
92
|
+
|
93
|
+
Sets the environment during which the cache functionality is active.
|
94
|
+
Default is: <tt>:production</tt>
|
95
|
+
|
96
|
+
|
97
|
+
==== <tt>:cache_page_extension</tt>+
|
98
|
+
|
99
|
+
Sets the default file extension for cached files.
|
100
|
+
Default is: <tt>.html</tt>
|
101
|
+
|
102
|
+
|
103
|
+
==== <tt>:cache_output_dir</tt>
|
104
|
+
|
105
|
+
Sets cache directory where the cached files are stored.
|
106
|
+
Default is: == "/path/2/your/app/public"
|
107
|
+
|
108
|
+
Although you can set it to the more ideal '<tt>..public/system/cache/</tt>'
|
109
|
+
if you can get that to work with your webserver setup.
|
110
|
+
|
111
|
+
|
112
|
+
==== <tt>:cache_fragments_output_dir</tt>
|
113
|
+
|
114
|
+
Sets the directory where cached fragments are stored.
|
115
|
+
Default is the '../tmp/cache_fragments/' directory at the root of your app.
|
116
|
+
|
117
|
+
This is for security reasons since you don't really want your cached fragments publically available.
|
118
|
+
|
119
|
+
|
120
|
+
==== <tt>:cache_fragments_wrap_with_html_comments</tt>
|
121
|
+
|
122
|
+
This setting toggles the wrapping of cached fragments in HTML comments. (see below)
|
123
|
+
Default is: <tt>true</tt>
|
124
|
+
|
125
|
+
|
126
|
+
==== <tt>:cache_logging</tt>
|
127
|
+
|
128
|
+
This setting toggles the logging of various cache calls. If the app has access to the <tt>#logger</tt> method,
|
129
|
+
curtesy of Sinatra::Logger[http://github.com/kematzy/sinatra-logger] then it will log there, otherwise logging
|
130
|
+
is silent.
|
131
|
+
|
132
|
+
Default is: <tt>true</tt>
|
133
|
+
|
134
|
+
|
135
|
+
==== <tt>:cache_logging_level</tt>
|
136
|
+
|
137
|
+
Sets the level at which the cache logger should log it's messages.
|
138
|
+
Default is: <tt>:info</tt>
|
139
|
+
|
140
|
+
Available options are: [:fatal, :error, :warn, :info, :debug]
|
141
|
+
|
142
|
+
|
143
|
+
== Basic Page Caching
|
144
|
+
|
145
|
+
By default caching only happens in <tt>:production</tt> mode, and via the Sinatra render methods, erb(), etc,
|
146
|
+
|
147
|
+
So asuming we have the following setup (continued from above)
|
148
|
+
|
149
|
+
|
150
|
+
class YourApp
|
151
|
+
|
152
|
+
<snip...>
|
153
|
+
|
154
|
+
set :cache_output_dir, "/full/path/2/app/root/public/system/cache"
|
155
|
+
|
156
|
+
<snip...>
|
157
|
+
|
158
|
+
get('/') { erb(:index) } # => cached as '../index.html'
|
159
|
+
|
160
|
+
get('/contact') { erb(:contact) } # => cached as '../contact.html'
|
161
|
+
|
162
|
+
# NB! the trailing slash on the URL
|
163
|
+
get('/about/') { erb(:about) } # => cached as '../about/index.html'
|
164
|
+
|
165
|
+
get('/feed.rss') { builder(:feed) } # => cached as '../feed.rss'
|
166
|
+
# NB! uses the extension of the passed URL,
|
167
|
+
# but DOES NOT ensure the format of the content based on the extension provided.
|
168
|
+
|
169
|
+
# complex URL with multiple possible params
|
170
|
+
get %r{/articles/?([\s\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?/?([\w-]+)?} do
|
171
|
+
erb(:articles)
|
172
|
+
end
|
173
|
+
# with the '/articles/a/b/c => cached as ../articles/a/b/c.html
|
174
|
+
|
175
|
+
# NB! the trailing slash on the URL
|
176
|
+
# with the '/articles/a/b/c/ => cached as ../articles/a/b/c/index.html
|
177
|
+
|
178
|
+
# CSS caching via Sass # => cached as '.../css/screen.css'
|
179
|
+
get '/css/screen.css' do
|
180
|
+
content_type 'text/css'
|
181
|
+
sass(:'css/screen')
|
182
|
+
end
|
183
|
+
|
184
|
+
# to turn off caching on certain pages.
|
185
|
+
get('/dont/cache/this/page') { erb(:aview, :cache => false) } # => is NOT cached
|
186
|
+
|
187
|
+
|
188
|
+
# NB! any query string params - [ /?page=X&id=y ] - are stripped off and TOTALLY IGNORED
|
189
|
+
# during the caching process.
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
OK, that's about all you need to know about basic Page Caching right there. Read the above example
|
194
|
+
carefully until you understand all the variations.
|
195
|
+
|
196
|
+
|
197
|
+
== Fragment Caching
|
198
|
+
|
199
|
+
If you just need to cache a fragment of a page, then you'd do as follows:
|
200
|
+
|
201
|
+
class YourApp
|
202
|
+
|
203
|
+
set :cache_fragments_output_dir, "/full/path/2/fragments/store/location"
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
Then in your views / layouts add the following:
|
208
|
+
|
209
|
+
<% cache_fragment(:name_of_fragment) do %>
|
210
|
+
# do something worth caching
|
211
|
+
<% end %>
|
212
|
+
|
213
|
+
|
214
|
+
Each fragment is stored in the same directory structure as your request
|
215
|
+
so, if you have a request like this:
|
216
|
+
|
217
|
+
get '/articles/2010/02' ...
|
218
|
+
|
219
|
+
...the cached fragment will be stored as:
|
220
|
+
|
221
|
+
../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
|
222
|
+
|
223
|
+
This enables you to use similar names for your fragments or have
|
224
|
+
multiple URLs use the same view / layout.
|
225
|
+
|
226
|
+
|
227
|
+
=== An important limitation
|
228
|
+
|
229
|
+
The fragment caching is dependent upon the final URL, so in the case of
|
230
|
+
a blog, where each article uses the same view, but through different URLs,
|
231
|
+
each of the articles would cache it's own fragment, which is ineffecient.
|
232
|
+
|
233
|
+
To sort-of deal with this limitation I have temporarily added a very hackish
|
234
|
+
'fix' through adding a 2nd parameter (see example below), which will remove the
|
235
|
+
last part of the URL and use the rest of the URL as the stored fragment path.
|
236
|
+
|
237
|
+
So given the URL:
|
238
|
+
|
239
|
+
get '/articles/2010/02/fragment-caching-with-sinatra-cache' ...
|
240
|
+
|
241
|
+
and the following <tt>#cache_fragment</tt> declaration in your view
|
242
|
+
|
243
|
+
<% cache_fragment(:name_of_fragment, :shared) do %>
|
244
|
+
# do something worth caching
|
245
|
+
<% end %>
|
246
|
+
|
247
|
+
...the cached fragment would be stored as:
|
248
|
+
|
249
|
+
../tmp/cache_fragments/articles/2010/02/< name_of_fragment >.html
|
250
|
+
|
251
|
+
Any other URLs with the same URL root, like...
|
252
|
+
|
253
|
+
get '/articles/2010/02/writing-sinatra-extensions' ...
|
254
|
+
|
255
|
+
... would use the same cached fragment.
|
256
|
+
|
257
|
+
|
258
|
+
<b>NB!</b> currently only supports one level, but Your fork might fix that ;-)
|
259
|
+
|
260
|
+
|
261
|
+
== Cache Expiration
|
262
|
+
|
263
|
+
<b>Under development, and not entirely final.</b> See Todo's below for more info.
|
264
|
+
|
265
|
+
|
266
|
+
To expire a cached item - file or fragment you use the :cache_expire() method.
|
267
|
+
|
268
|
+
|
269
|
+
cache_expire('/contact') => expires ../contact.html
|
270
|
+
|
271
|
+
|
272
|
+
# NB! notice the trailing slash
|
273
|
+
cache_expire('/contact/') => expires ../contact/index.html
|
274
|
+
|
275
|
+
|
276
|
+
cache_expire('/feed.rss') => expires ../feed.rss
|
277
|
+
|
278
|
+
|
279
|
+
To expire a cached fragment:
|
280
|
+
|
281
|
+
cache_expire('/some/path', :fragment => :name_of_fragment )
|
282
|
+
|
283
|
+
=> expires ../some/path/:name_of_fragment.html
|
284
|
+
|
285
|
+
|
286
|
+
|
287
|
+
== A few important points to consider
|
288
|
+
|
289
|
+
|
290
|
+
=== The DANGERS of URL query string params
|
291
|
+
|
292
|
+
By default the caching ignores the query string params, but that's not the only problem with query params.
|
293
|
+
|
294
|
+
Let's say you have a URL like this:
|
295
|
+
|
296
|
+
/products/?product_id=111
|
297
|
+
|
298
|
+
and then inside that template [ .../views/products.erb ], you use the <tt>params[:product_id]</tt>
|
299
|
+
param passed in for some purpose.
|
300
|
+
|
301
|
+
<ul>
|
302
|
+
<li>Product ID: <%= params[:product_id] %></li> # => 111
|
303
|
+
...
|
304
|
+
</ul>
|
305
|
+
|
306
|
+
If you cache this URL, then the cached file [ ../cache/products.html ] will be stored with that
|
307
|
+
value embedded. Obviously not ideal for any other similar URLs with different <tt>product_id</tt>'s
|
308
|
+
|
309
|
+
To overcome this issue, use either of these two methods.
|
310
|
+
|
311
|
+
# in your_app.rb
|
312
|
+
|
313
|
+
# turning off caching on this page
|
314
|
+
|
315
|
+
get '/products/' do
|
316
|
+
...
|
317
|
+
erb(:products, :cache => false)
|
318
|
+
end
|
319
|
+
|
320
|
+
# or
|
321
|
+
|
322
|
+
# rework the URLs to something like '/products/111 '
|
323
|
+
|
324
|
+
get '/products/:product_id' do
|
325
|
+
...
|
326
|
+
erb(:products)
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
|
331
|
+
Thats's about all the information you need to know.
|
332
|
+
|
333
|
+
|
334
|
+
== RTFM
|
335
|
+
|
336
|
+
If the above is not clear enough, please check the Specs for a better understanding.
|
337
|
+
|
338
|
+
|
339
|
+
== Errors / Bugs
|
340
|
+
|
341
|
+
If something is not behaving intuitively, it is a bug, and should be reported.
|
342
|
+
Report it here: http://github.com/kematzy/sinatra-cache/issues
|
343
|
+
|
344
|
+
|
345
|
+
== TODOs
|
346
|
+
|
347
|
+
* Improve the fragment caching functionality
|
348
|
+
|
349
|
+
* Decide on how to handle site-wide shared fragments.
|
350
|
+
|
351
|
+
* Make the shared fragments more dynamic or usable
|
352
|
+
|
353
|
+
* Work out how to use the <tt>cache_expire()</tt> functionality in a logical way.
|
354
|
+
|
355
|
+
* Work out and include instructions on how to use a '../public/custom/cache/dir' with Passenger.
|
356
|
+
|
357
|
+
* Write more tests to ensure everything is very solid.
|
358
|
+
|
359
|
+
* Any other improvements you or I can think of.
|
360
|
+
|
361
|
+
|
362
|
+
== Note on Patches/Pull Requests
|
363
|
+
|
364
|
+
* Fork the project.
|
365
|
+
* Make your feature addition or bug fix.
|
366
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
367
|
+
* Commit, do not mess with rakefile, version, or history.
|
368
|
+
* (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)
|
369
|
+
* Send me a pull request. Bonus points for topic branches.
|
370
|
+
|
371
|
+
== Copyright
|
372
|
+
|
373
|
+
Copyright (c) 2009-2010 kematzy. Released under the MIT License.
|
374
|
+
|
375
|
+
See LICENSE for details.
|
376
|
+
|
377
|
+
=== Credits
|
378
|
+
|
379
|
+
Included in this gem is code from <tt>SinatraMore::OutputHelpers</tt>, taken from the
|
380
|
+
<tt>sinatra_more</tt> gem [ http://github.com/nesquena/sinatra_more/ ] by Nathan Esquenazi.
|
381
|
+
|
382
|
+
The code was renamed to Sinatra::Output to prevent any extension clashes.
|
383
|
+
|
384
|
+
Copyright (c) 2009 Nathan Esquenazi. Released under the MIT License.
|
385
|
+
|
386
|
+
|
387
|
+
A big <b>Thank You!</b> goes to rtomayko[http://github/rtomayko], blakemizerany[http://github.com/blakemizerany/]
|
388
|
+
and others working on the Sinatra framework.
|
389
|
+
|
390
|
+
=== Inspirations
|
391
|
+
|
392
|
+
Inspired by code from Rails[http://rubyonrails.com/] & Merb[http://merbivore.com/]
|
393
|
+
and other sources
|
394
|
+
|
data/Rakefile
CHANGED
@@ -5,42 +5,85 @@ begin
|
|
5
5
|
require 'jeweler'
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "sinatra-cache"
|
8
|
-
gem.summary = %Q{
|
8
|
+
gem.summary = %Q{A Sinatra Extension that makes Page and Fragment Caching easy.}
|
9
|
+
gem.description = %Q{A Sinatra Extension that makes Page and Fragment Caching easy.}
|
9
10
|
gem.email = "kematzy@gmail.com"
|
10
11
|
gem.homepage = "http://github.com/kematzy/sinatra-cache"
|
11
|
-
gem.description = "Simple Page Caching for Sinatra [www.sinatrarb.com]"
|
12
12
|
gem.authors = ["kematzy"]
|
13
|
+
gem.add_dependency('sinatra', '>=1.0.a')
|
14
|
+
# gem.add_dependency('dependency', '>=x.x.x')
|
15
|
+
gem.add_development_dependency "sinatra-tests", ">= 0.1.6"
|
16
|
+
gem.add_development_dependency "rspec", ">= 1.3.0"
|
17
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
13
18
|
end
|
14
19
|
rescue LoadError
|
15
|
-
puts "Jeweler not available. Install it with: sudo gem install
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
16
21
|
end
|
17
22
|
|
23
|
+
require 'spec/rake/spectask'
|
24
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
25
|
+
spec.libs << 'lib' << 'spec'
|
26
|
+
spec.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
|
27
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
28
|
+
end
|
29
|
+
|
30
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
31
|
+
spec.libs << 'lib' << 'spec'
|
32
|
+
spec.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
|
33
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
34
|
+
spec.rcov = true
|
35
|
+
end
|
36
|
+
|
37
|
+
namespace :spec do
|
38
|
+
|
39
|
+
desc "Run all specifications verbosely"
|
40
|
+
Spec::Rake::SpecTask.new(:verbose) do |t|
|
41
|
+
t.libs << "lib"
|
42
|
+
t.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "Run specific spec verbosely (SPEC=/path/2/file)"
|
46
|
+
Spec::Rake::SpecTask.new(:select) do |t|
|
47
|
+
t.libs << "lib"
|
48
|
+
t.spec_files = [ENV["SPEC"]]
|
49
|
+
t.spec_opts = ["--color", "--format", "specdoc", "--require", "spec/spec_helper.rb"]
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
task :spec => :check_dependencies
|
55
|
+
|
56
|
+
task :default => :spec
|
57
|
+
|
18
58
|
require 'rake/rdoctask'
|
19
59
|
Rake::RDocTask.new do |rdoc|
|
60
|
+
version = File.exist?('VERSION') ? IO.read('VERSION').chomp : "[Unknown]"
|
61
|
+
|
20
62
|
rdoc.rdoc_dir = 'rdoc'
|
21
|
-
rdoc.title =
|
22
|
-
rdoc.options << '--line-numbers' << '--inline-source'
|
63
|
+
rdoc.title = "Sinatra::Cache v#{version}"
|
23
64
|
rdoc.rdoc_files.include('README*')
|
24
65
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
66
|
end
|
26
67
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
68
|
+
desc 'Build the rdoc HTML Files'
|
69
|
+
task :docs do
|
70
|
+
version = File.exist?('VERSION') ? IO.read('VERSION').chomp : "[Unknown]"
|
71
|
+
|
72
|
+
sh "sdoc -N --title 'Sinatra::Cache v#{version}' lib/ README.rdoc"
|
32
73
|
end
|
33
74
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
75
|
+
namespace :docs do
|
76
|
+
|
77
|
+
desc 'Remove rdoc products'
|
78
|
+
task :remove => [:clobber_rdoc]
|
79
|
+
|
80
|
+
desc 'Force a rebuild of the RDOC files'
|
81
|
+
task :rebuild => [:rerdoc]
|
82
|
+
|
83
|
+
desc 'Build docs, and open in browser for viewing (specify BROWSER)'
|
84
|
+
task :open => [:docs] do
|
85
|
+
browser = ENV["BROWSER"] || "safari"
|
86
|
+
sh "open -a #{browser} doc/index.html"
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|