merb-cache 0.9.7 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +2 -2
- data/README +207 -143
- data/Rakefile +55 -10
- data/TODO +0 -2
- data/lib/merb-cache/cache.rb +84 -0
- data/lib/merb-cache/core_ext/enumerable.rb +9 -0
- data/lib/merb-cache/core_ext/hash.rb +20 -0
- data/lib/merb-cache/merb_ext/controller.rb +167 -0
- data/lib/merb-cache/stores/fundamental/abstract_store.rb +101 -0
- data/lib/merb-cache/stores/fundamental/file_store.rb +112 -0
- data/lib/merb-cache/stores/fundamental/memcached_store.rb +112 -0
- data/lib/merb-cache/stores/strategy/abstract_strategy_store.rb +123 -0
- data/lib/merb-cache/stores/strategy/action_store.rb +56 -0
- data/lib/merb-cache/stores/strategy/adhoc_store.rb +69 -0
- data/lib/merb-cache/stores/strategy/gzip_store.rb +63 -0
- data/lib/merb-cache/stores/strategy/page_store.rb +64 -0
- data/lib/merb-cache/stores/strategy/sha1_store.rb +62 -0
- data/lib/merb-cache.rb +8 -7
- data/spec/merb-cache/cache_spec.rb +88 -0
- data/spec/merb-cache/core_ext/enumerable_spec.rb +22 -0
- data/spec/merb-cache/core_ext/hash_spec.rb +20 -0
- data/spec/merb-cache/merb_ext/controller_spec.rb +284 -0
- data/spec/merb-cache/stores/fundamental/abstract_store_spec.rb +166 -0
- data/spec/merb-cache/stores/fundamental/file_store_spec.rb +186 -0
- data/spec/merb-cache/stores/fundamental/memcached_store_spec.rb +243 -0
- data/spec/merb-cache/stores/strategy/abstract_strategy_store_spec.rb +78 -0
- data/spec/merb-cache/stores/strategy/action_store_spec.rb +189 -0
- data/spec/merb-cache/stores/strategy/adhoc_store_spec.rb +225 -0
- data/spec/merb-cache/stores/strategy/gzip_store_spec.rb +51 -0
- data/spec/merb-cache/stores/strategy/page_store_spec.rb +111 -0
- data/spec/merb-cache/stores/strategy/sha1_store_spec.rb +75 -0
- data/spec/spec_helper.rb +69 -72
- metadata +42 -31
- data/lib/merb-cache/cache-action.rb +0 -144
- data/lib/merb-cache/cache-fragment.rb +0 -95
- data/lib/merb-cache/cache-page.rb +0 -203
- data/lib/merb-cache/cache-store/database-activerecord.rb +0 -88
- data/lib/merb-cache/cache-store/database-datamapper.rb +0 -79
- data/lib/merb-cache/cache-store/database-sequel.rb +0 -78
- data/lib/merb-cache/cache-store/database.rb +0 -144
- data/lib/merb-cache/cache-store/dummy.rb +0 -106
- data/lib/merb-cache/cache-store/file.rb +0 -194
- data/lib/merb-cache/cache-store/memcache.rb +0 -199
- data/lib/merb-cache/cache-store/memory.rb +0 -168
- data/lib/merb-cache/merb-cache.rb +0 -165
- data/lib/merb-cache/merbtasks.rb +0 -6
- data/spec/config/database.yml +0 -14
- data/spec/controller.rb +0 -101
- data/spec/log/merb_test.log +0 -433
- data/spec/merb-cache-action_spec.rb +0 -162
- data/spec/merb-cache-fragment_spec.rb +0 -100
- data/spec/merb-cache-page_spec.rb +0 -150
- data/spec/merb-cache_spec.rb +0 -15
- data/spec/views/cache_controller/action1.html.erb +0 -4
- data/spec/views/cache_controller/action2.html.haml +0 -4
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2008
|
1
|
+
Copyright (c) 2008 Ben Burkert
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining
|
4
4
|
a copy of this software and associated documentation files (the
|
@@ -17,4 +17,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
17
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
18
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
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.
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
CHANGED
@@ -1,160 +1,224 @@
|
|
1
|
-
|
1
|
+
merb-cache
|
2
|
+
==========
|
2
3
|
|
3
|
-
A plugin for the Merb framework that provides caching
|
4
|
+
A plugin for the Merb framework that provides caching stores,
|
5
|
+
strategies and helpers.
|
4
6
|
|
5
|
-
Currently supported methods:
|
6
7
|
|
7
|
-
- page caching:
|
8
|
-
- action caching
|
9
|
-
- fragment caching
|
10
|
-
- object caching
|
11
8
|
|
12
|
-
|
9
|
+
Tutorial
|
10
|
+
==========
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
Stores usually set up in application init file
|
13
|
+
(init.rb) or environment specific init file (so you can
|
14
|
+
use different stores for production, staging and development
|
15
|
+
environment if you need to).
|
18
16
|
|
19
|
-
|
20
|
-
With fragment caching, you can mix dynamic and static content.
|
17
|
+
# create a fundamental memcache store named :memcached for localhost
|
21
18
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
With page caching, the whole template is put in html files in a special
|
26
|
-
directory in order to be handled directly without triggering Merb.
|
27
|
-
|
28
|
-
== Quick API
|
29
|
-
|
30
|
-
=== Merb::Controller class methods
|
31
|
-
cache_action(action, expiration)
|
32
|
-
cache_actions(action, [action, expiration], ...)
|
33
|
-
cache_page(action, expiration)
|
34
|
-
cache_pages(action, [action, expiration], ...)
|
35
|
-
|
36
|
-
=== Merb::Controller instance methods
|
37
|
-
expire_page(key)
|
38
|
-
cached_page?(key)
|
39
|
-
expire_all_pages()
|
40
|
-
|
41
|
-
expire_action(key)
|
42
|
-
cached_action?(key)
|
43
|
-
|
44
|
-
cached?(key)
|
45
|
-
cache_get(key)
|
46
|
-
cache_set(key, data, expiration)
|
47
|
-
expire(key)
|
48
|
-
expire_all()
|
49
|
-
|
50
|
-
=== Inside your template
|
51
|
-
cache(key, expiration) do ... end
|
52
|
-
|
53
|
-
# expiration is given in minutes
|
54
|
-
|
55
|
-
# key can be a string or a hash
|
56
|
-
# possible keys when it's a hash:
|
57
|
-
# :key (full key)
|
58
|
-
# :params (array of params to be added to the key)
|
59
|
-
# :action, :controller
|
60
|
-
# :match (true or partial key)
|
61
|
-
|
62
|
-
# Don't forget to look at the specs !!
|
63
|
-
|
64
|
-
== Specs
|
65
|
-
$ rake specs:<cache_store>
|
66
|
-
example:
|
67
|
-
$ rake specs:memory
|
68
|
-
$ rake specs:file
|
69
|
-
or just:
|
70
|
-
$ cd spec
|
71
|
-
$ STORE=<cache_store> spec merb-cache_spec.rb
|
72
|
-
# cache_store can be:
|
73
|
-
# memory, memcache, file, sequel, datamapper, activerecord
|
74
|
-
|
75
|
-
== Sample configuration
|
76
|
-
|
77
|
-
Merb::Plugins.config[:merb_cache] = {
|
78
|
-
:cache_html_directory => Merb.dir_for(:public) / "cache",
|
79
|
-
|
80
|
-
#:store => "database",
|
81
|
-
#:table_name => "merb_cache",
|
82
|
-
|
83
|
-
#:disable => "development", # disable merb-cache in development
|
84
|
-
#:disable => true, # disable merb-cache in all environments
|
85
|
-
|
86
|
-
:store => "file",
|
87
|
-
:cache_directory => Merb.root_path("tmp/cache"),
|
88
|
-
|
89
|
-
#:store => "memcache",
|
90
|
-
#:host => "127.0.0.1:11211",
|
91
|
-
#:namespace => "merb_cache",
|
92
|
-
#:no_tracking => false,
|
93
|
-
|
94
|
-
#:store => "memory",
|
95
|
-
# store could be: file, memcache, memory, database, dummy, ...
|
96
|
-
}
|
97
|
-
|
98
|
-
|
99
|
-
== Quick Example
|
100
|
-
|
101
|
-
==== controller part
|
102
|
-
class Users < Merb::Controller
|
103
|
-
cache_page :action_name
|
104
|
-
# this will cache the action in public/cache/something.html
|
105
|
-
# this cache entry will never expire (no expiration provided)
|
106
|
-
# for permanent caching you could set your lighty/nginx so as to handle
|
107
|
-
# the .html file directly
|
108
|
-
# for multiple page caching:
|
109
|
-
# cache_pages :action_name, [:another_action, 5], :some_action
|
110
|
-
|
111
|
-
cache_action :another_action, 10
|
112
|
-
# this will cache the action using the cache store
|
113
|
-
# this cache entry will expire in 10 minutes
|
114
|
-
# for multiple action caching:
|
115
|
-
# cache_actions :action_name, [:another_action, 5], :some_action
|
116
|
-
|
117
|
-
def list
|
118
|
-
unless @users = cache_get("active_users")
|
119
|
-
@users = User.all(:active => true)
|
120
|
-
cache_set("active_users", @users)
|
121
|
-
# object caching can be used to avoid pulling huge amounts of data
|
122
|
-
# from the database.
|
123
|
-
# you could have calle cache_set with an expiration time as well:
|
124
|
-
# cache_set("active_users", @users, 10)
|
125
|
-
end
|
126
|
-
render
|
127
|
-
end
|
19
|
+
Merb::Cache.setup do
|
20
|
+
register(:memcached, MemcachedStore, :namespace => "my_app", :servers => ["127.0.0.1:11211"])
|
21
|
+
end
|
128
22
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
end
|
23
|
+
# a default FileStore
|
24
|
+
Merb::Cache.setup do
|
25
|
+
register(FileStore)
|
26
|
+
end
|
134
27
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
28
|
+
# another FileStore
|
29
|
+
Merb::Cache.setup do
|
30
|
+
register(:tmp_cache, FileStore, :dir => "/tmp")
|
31
|
+
end
|
32
|
+
|
33
|
+
Now lets see how we can use stores in the application:
|
139
34
|
|
140
|
-
|
141
|
-
|
142
|
-
|
35
|
+
class Tag
|
36
|
+
def find(parameters = {})
|
37
|
+
# poor man's identity map
|
38
|
+
|
39
|
+
if Merb::Cache[:memcached].exists?("tags", parameters)
|
40
|
+
Merb::Cache[:memcached].read("tags", parameters)
|
41
|
+
else
|
42
|
+
results = super(parameters)
|
43
|
+
Merb::Cache[:memcached].write("tags", results, parameters)
|
44
|
+
|
45
|
+
results
|
143
46
|
end
|
47
|
+
end
|
144
48
|
|
145
|
-
|
146
|
-
|
49
|
+
def popularity_rating
|
50
|
+
# lets keep the popularity rating cached for 30 seconds
|
51
|
+
# merb-cache will create a key from the model's id & the interval parameter
|
52
|
+
|
53
|
+
Merb::Cache[:memcached].fetch(self.id, :interval => Time.now.to_i / 30) do
|
54
|
+
self.run_long_popularity_rating_query
|
147
55
|
end
|
148
56
|
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
Or, if you want to use memcache’s built in expire option:
|
61
|
+
|
62
|
+
# expire a cache entry for "bar" (identified by the key "foo" and
|
63
|
+
# parameters {:baz => :bay}) in two hours
|
64
|
+
Merb::Cache[:memcached].write("foo", "bar", {:baz => :bay}, :expire_in => 2.hours)
|
65
|
+
|
66
|
+
# this will fail, because FileStore cannot expire cache entries
|
67
|
+
Merb::Cache[:default].write("foo", "bar", {:baz => :bay}, :expire_in => 2.hours)
|
68
|
+
|
69
|
+
# writing to the FileStore will fail, but the MemcachedStore will succeed
|
70
|
+
Merb::Cache[:default, :memcached].write("foo", "bar", {:baz => :bay}, :expire_in => 2.hours)
|
149
71
|
|
72
|
+
# this will fail
|
73
|
+
Merb::Cache[:default, :memcached].write_all("foo", "bar", {:baz => :bay}, :expire_in => 2.hours)
|
150
74
|
|
151
|
-
====views/users/index.html.erb
|
152
|
-
# this entry will expire in 10 minutes
|
153
|
-
<%- cache "users_index", 10 do %>
|
154
|
-
<div>some big template</div>
|
155
|
-
<% end -%>
|
156
75
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
76
|
+
Setting up strategy stores is very similar to fundamental stores:
|
77
|
+
|
78
|
+
Merb::Cache.setup do
|
79
|
+
|
80
|
+
# wraps the :memcached store we setup earlier
|
81
|
+
register(:zipped, GzipStore[:memcached])
|
82
|
+
|
83
|
+
# wrap a strategy store
|
84
|
+
register(:sha_and_zip, SHA1Store[:zipped])
|
85
|
+
|
86
|
+
# you can even use unnamed fundamental stores
|
87
|
+
register(:zipped_images, GzipStore[FileStore],
|
88
|
+
:dir => Merb.root / "public" / "images")
|
89
|
+
|
90
|
+
# or a combination or strategy & fundamental stores
|
91
|
+
register(:secured, SHA1Store[GzipStore[FileStore], FileStore],
|
92
|
+
:dir => Merb.root / "private")
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
You can use these strategy stores exactly like fundamental stores in your app code.
|
97
|
+
|
98
|
+
Action & Page Caching
|
99
|
+
Action & page caching have been implemented in strategy stores. So instead of manually specifying which type of caching you want for each action, you simply ask merb-cache to cache your action, and it will use the fastest cache available.
|
100
|
+
|
101
|
+
First, let’s setup our page & action stores:
|
102
|
+
|
103
|
+
config/environments/development.rb
|
104
|
+
|
105
|
+
Merb::Cache.setup do
|
106
|
+
|
107
|
+
# the order that stores are setup is important
|
108
|
+
# faster stores should be setup first
|
109
|
+
|
110
|
+
# page cache to the public dir
|
111
|
+
register(:page_store, PageStore[FileStore],
|
112
|
+
:dir => Merb.root / "public")
|
113
|
+
|
114
|
+
# action cache to memcache
|
115
|
+
register(:action_store, ActionStore[:sha_and_zip])
|
116
|
+
|
117
|
+
# sets up the ordering of stores when attempting to read/write cache entries
|
118
|
+
register(:default, AdhocStore[:page_store, :action_store])
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
And now in our controller:
|
123
|
+
class Tags < Merb::Controller
|
124
|
+
|
125
|
+
# index & show will be page cached to the public dir. The index
|
126
|
+
# action has no parameters, and the show parameter's are part of
|
127
|
+
# the URL, making them both page-cache'able
|
128
|
+
cache :index, :show
|
129
|
+
|
130
|
+
def index
|
131
|
+
render
|
132
|
+
end
|
133
|
+
|
134
|
+
def show(:slug)
|
135
|
+
display Tag.first(:slug => slug)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
Our controller now page caches but the index & show action. Furthermore,
|
140
|
+
the show action is cached separately for each slug parameter automatically.
|
141
|
+
|
142
|
+
|
143
|
+
class Tags < Merb::Controller
|
144
|
+
|
145
|
+
# the term is a route param, while the page & per_page params are part of the query string.
|
146
|
+
# If only the term param is supplied, the request can be page cached, but if the page and/or
|
147
|
+
# per_page param is part of the query string, the request will action cache.
|
148
|
+
cache :catalog
|
149
|
+
|
150
|
+
def catalog(term = 'a', page = 1, per_page = 20)
|
151
|
+
@tags = Tag.for_term(term).paginate(page, per_page)
|
152
|
+
|
153
|
+
display @tags
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
Because the specific type of caching is not specified, the same action can either
|
158
|
+
be page cached or action cached depending on the context of the request.
|
159
|
+
|
160
|
+
|
161
|
+
Keeping a “Hot” Cache
|
162
|
+
=====================
|
163
|
+
|
164
|
+
Cache expiration is a constant problem for developers. When should content
|
165
|
+
be expired? Should we “sweep” stale content? How do we balance serving fresh
|
166
|
+
content and maintaining fast response times? These are difficult questions
|
167
|
+
for developers, and are usually answered with ugly code added across our
|
168
|
+
models, views, and controllers. Instead of designing an elaborate
|
169
|
+
caching and expiring system, an alternate approach is to keep a “hot” cache.
|
170
|
+
|
171
|
+
So what is a “hot” cache? A hot cache is what you get when you ignore
|
172
|
+
trying to manually expire content, and instead focus on replacing old
|
173
|
+
content with fresh data as soon as it becomes stale. Keeping a hot
|
174
|
+
cache means no difficult expiration logic spread out across your
|
175
|
+
app, and will all but eliminate cache misses.
|
176
|
+
|
177
|
+
The problem until now with this approach has been the impact on
|
178
|
+
response times. If the request has to wait on any pages that
|
179
|
+
it has made stale to render the fresh version, it can slow down
|
180
|
+
the response time dramatically. Thankfully, Merb has the run_later
|
181
|
+
method which allows the fresh content to render after the
|
182
|
+
response has been sent to the browser.
|
183
|
+
It’s the best of both worlds. Here’s an example.
|
184
|
+
|
185
|
+
|
186
|
+
class Tags < Merb::Controller
|
187
|
+
|
188
|
+
cache :index
|
189
|
+
eager_cache :create, :index
|
190
|
+
|
191
|
+
def index
|
192
|
+
display Tag.all
|
193
|
+
end
|
194
|
+
|
195
|
+
def create(slug)
|
196
|
+
@tag = Tag.new(slug)
|
197
|
+
|
198
|
+
# redirect them back to the index action
|
199
|
+
redirect url(:tags)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
The controller will eager_cache the index action whenever the create action
|
204
|
+
is successfully called. If the client were to post a new tag to the
|
205
|
+
create action, they would be redirect back to the index action.
|
206
|
+
Right after the response had been sent to the client, the index action
|
207
|
+
would be rendered with the newly created tag included and replaced
|
208
|
+
in the cache. So when the user requests for the index action gets
|
209
|
+
to the server, the freshest version is already in the cache, and
|
210
|
+
the cache miss is avoided. This works regardless of the way
|
211
|
+
the index action is cached.
|
212
|
+
|
213
|
+
Hot cache helps fight dog pile effect
|
214
|
+
(http://highscalability.com/strategy-break-memcache-dog-pile) but
|
215
|
+
should be used with caution. It's great when you want to eagerly cache
|
216
|
+
some page that user is not going to see immediately after
|
217
|
+
creating/updating something because hot cache in current implementation
|
218
|
+
uses worker queue (knows as run_later) and it does not guarantee that
|
219
|
+
before redirect hits the action data is gonna be already cached.
|
220
|
+
|
221
|
+
A good use case of eager caching is front end page of
|
222
|
+
some newspaper site when staff updates site content, and
|
223
|
+
is not redirected to page that uses new cache values immediately,
|
224
|
+
but other users access it frequently.
|
data/Rakefile
CHANGED
@@ -2,6 +2,7 @@ require 'rubygems'
|
|
2
2
|
require 'rake/gempackagetask'
|
3
3
|
require "extlib"
|
4
4
|
require 'merb-core/tasks/merb_rake_helper'
|
5
|
+
require "spec/rake/spectask"
|
5
6
|
|
6
7
|
##############################################################################
|
7
8
|
# Package && release
|
@@ -11,12 +12,12 @@ PROJECT_URL = "http://merbivore.com"
|
|
11
12
|
PROJECT_SUMMARY = "Merb plugin that provides caching (page, action, fragment, object)"
|
12
13
|
PROJECT_DESCRIPTION = PROJECT_SUMMARY
|
13
14
|
|
14
|
-
GEM_AUTHOR = "
|
15
|
-
GEM_EMAIL = "
|
15
|
+
GEM_AUTHOR = "Ben Burkert"
|
16
|
+
GEM_EMAIL = "ben@benburkert.com"
|
16
17
|
|
17
18
|
GEM_NAME = "merb-cache"
|
18
19
|
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
19
|
-
GEM_VERSION = (Merb::MORE_VERSION rescue "0.9.
|
20
|
+
GEM_VERSION = (Merb::MORE_VERSION rescue "0.9.8") + PKG_BUILD
|
20
21
|
|
21
22
|
RELEASE_NAME = "REL #{GEM_VERSION}"
|
22
23
|
|
@@ -34,7 +35,7 @@ spec = Gem::Specification.new do |s|
|
|
34
35
|
s.author = GEM_AUTHOR
|
35
36
|
s.email = GEM_EMAIL
|
36
37
|
s.homepage = PROJECT_URL
|
37
|
-
s.add_dependency('merb-core', '>= 0.9.
|
38
|
+
s.add_dependency('merb-core', '>= 0.9.8')
|
38
39
|
s.require_path = 'lib'
|
39
40
|
s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
|
40
41
|
end
|
@@ -44,15 +45,59 @@ Rake::GemPackageTask.new(spec) do |pkg|
|
|
44
45
|
end
|
45
46
|
|
46
47
|
desc "Install the gem"
|
47
|
-
task :install
|
48
|
-
|
48
|
+
task :install do
|
49
|
+
Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
|
49
50
|
end
|
50
51
|
|
51
|
-
|
52
|
+
desc "Uninstall the gem"
|
53
|
+
task :uninstall do
|
54
|
+
Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
|
55
|
+
end
|
52
56
|
|
53
|
-
|
54
|
-
|
55
|
-
|
57
|
+
desc "Create a gemspec file"
|
58
|
+
task :gemspec do
|
59
|
+
File.open("#{GEM_NAME}.gemspec", "w") do |file|
|
60
|
+
file.puts spec.to_ruby
|
56
61
|
end
|
62
|
+
end
|
57
63
|
|
64
|
+
desc "Run all examples (or a specific spec with TASK=xxxx)"
|
65
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
66
|
+
t.spec_opts = ["-cfs"]
|
67
|
+
t.spec_files = begin
|
68
|
+
if ENV["TASK"]
|
69
|
+
ENV["TASK"].split(',').map { |task| "spec/**/#{task}_spec.rb" }
|
70
|
+
else
|
71
|
+
FileList['spec/**/*_spec.rb']
|
72
|
+
end
|
73
|
+
end
|
58
74
|
end
|
75
|
+
|
76
|
+
desc 'Default: run spec examples'
|
77
|
+
task :default => 'spec'
|
78
|
+
|
79
|
+
##############################################################################
|
80
|
+
# memcached
|
81
|
+
##############################################################################
|
82
|
+
MEMCACHED_PORTS = 43042..43043
|
83
|
+
|
84
|
+
namespace :memcached do
|
85
|
+
desc "Start the memcached instances for specs"
|
86
|
+
task :start do
|
87
|
+
log = "/tmp/memcached.log"
|
88
|
+
system ">#{log}"
|
89
|
+
|
90
|
+
verbosity = (ENV['DEBUG'] ? "-vv" : "")
|
91
|
+
|
92
|
+
(MEMCACHED_PORTS).each do |port|
|
93
|
+
system "memcached #{verbosity} -p #{port} >> #{log} 2>&1 &"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
desc "Kill the memcached instances"
|
98
|
+
task :kill do
|
99
|
+
`ps awx`.split("\n").grep(/#{MEMCACHED_PORTS.to_a.join('|')}/).map do |process|
|
100
|
+
system("kill -9 #{process.to_i}") rescue nil
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/TODO
CHANGED
@@ -0,0 +1,84 @@
|
|
1
|
+
module Merb
|
2
|
+
# A convinient way to get at Merb::Cache
|
3
|
+
def self.cache
|
4
|
+
Merb::Cache
|
5
|
+
end
|
6
|
+
|
7
|
+
module Cache
|
8
|
+
|
9
|
+
def self.setup(&blk)
|
10
|
+
instance_eval(&blk) unless blk.nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
# autoload is used so that gem dependencies can be required only when needed by
|
14
|
+
# adding the require statement in the store file.
|
15
|
+
autoload :AbstractStore, "merb-cache" / "stores" / "fundamental" / "abstract_store"
|
16
|
+
autoload :FileStore, "merb-cache" / "stores" / "fundamental" / "file_store"
|
17
|
+
autoload :MemcachedStore, "merb-cache" / "stores" / "fundamental" / "memcached_store"
|
18
|
+
|
19
|
+
autoload :AbstractStrategyStore, "merb-cache" / "stores" / "strategy" / "abstract_strategy_store"
|
20
|
+
autoload :ActionStore, "merb-cache" / "stores" / "strategy" / "action_store"
|
21
|
+
autoload :AdhocStore, "merb-cache" / "stores" / "strategy" / "adhoc_store"
|
22
|
+
autoload :GzipStore, "merb-cache" / "stores" / "strategy" / "gzip_store"
|
23
|
+
autoload :PageStore, "merb-cache" / "stores" / "strategy" / "page_store"
|
24
|
+
autoload :SHA1Store, "merb-cache" / "stores" / "strategy" / "sha1_store"
|
25
|
+
|
26
|
+
|
27
|
+
class << self
|
28
|
+
attr_accessor :stores
|
29
|
+
end
|
30
|
+
|
31
|
+
self.stores = {}
|
32
|
+
|
33
|
+
# Cache store lookup
|
34
|
+
# name<Symbol> : The name of a registered store
|
35
|
+
# Returns<Nil AbstractStore> : A thread-safe copy of the store
|
36
|
+
def self.[](*names)
|
37
|
+
if names.size == 1
|
38
|
+
Thread.current[:'merb-cache'] ||= {}
|
39
|
+
(Thread.current[:'merb-cache'][names.first] ||= stores[names.first].clone)
|
40
|
+
else
|
41
|
+
AdhocStore[*names]
|
42
|
+
end
|
43
|
+
rescue TypeError
|
44
|
+
raise(StoreNotFound, "Could not find the :#{names.first} store")
|
45
|
+
end
|
46
|
+
|
47
|
+
# Clones the cache stores for the current thread
|
48
|
+
def self.clone_stores
|
49
|
+
@stores.inject({}) {|h, (k, s)| h[k] = s.clone; h}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Registers the cache store name with a type & options
|
53
|
+
# name<Symbol> : An optional symbol to give the cache. :default is used if no name is given.
|
54
|
+
# klass<Class> : A store type.
|
55
|
+
# opts<Hash> : A hash to pass through to the store for configuration.
|
56
|
+
def self.register(name, klass = nil, opts = {})
|
57
|
+
klass, opts = nil, klass if klass.is_a? Hash
|
58
|
+
name, klass = default_store_name, name if klass.nil?
|
59
|
+
|
60
|
+
raise StoreExists, "#{name} store already setup" if @stores.has_key?(name)
|
61
|
+
|
62
|
+
@stores[name] = (AdhocStore === klass) ? klass : klass.new(opts)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Checks to see if a given store exists already.
|
66
|
+
def self.exists?(name)
|
67
|
+
return true if self[name]
|
68
|
+
rescue StoreNotFound
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
|
72
|
+
# Default store name is :default.
|
73
|
+
def self.default_store_name
|
74
|
+
:default
|
75
|
+
end
|
76
|
+
|
77
|
+
class NotSupportedError < Exception; end
|
78
|
+
|
79
|
+
class StoreExists < Exception; end
|
80
|
+
|
81
|
+
# Raised when requested store cannot be found on the list of registered.
|
82
|
+
class StoreNotFound < Exception; end
|
83
|
+
end #Cache
|
84
|
+
end #Merb
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
|
5
|
+
def to_sha2
|
6
|
+
string = ""
|
7
|
+
keys.sort_by{|k| k.to_s}.each do |k|
|
8
|
+
case self[k]
|
9
|
+
when Array
|
10
|
+
string << self[k].join
|
11
|
+
when Hash
|
12
|
+
string << self[k].to_sha2
|
13
|
+
else
|
14
|
+
string << self[k].to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
Digest::SHA2.hexdigest(string)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|