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.
Files changed (55) hide show
  1. data/LICENSE +2 -2
  2. data/README +207 -143
  3. data/Rakefile +55 -10
  4. data/TODO +0 -2
  5. data/lib/merb-cache/cache.rb +84 -0
  6. data/lib/merb-cache/core_ext/enumerable.rb +9 -0
  7. data/lib/merb-cache/core_ext/hash.rb +20 -0
  8. data/lib/merb-cache/merb_ext/controller.rb +167 -0
  9. data/lib/merb-cache/stores/fundamental/abstract_store.rb +101 -0
  10. data/lib/merb-cache/stores/fundamental/file_store.rb +112 -0
  11. data/lib/merb-cache/stores/fundamental/memcached_store.rb +112 -0
  12. data/lib/merb-cache/stores/strategy/abstract_strategy_store.rb +123 -0
  13. data/lib/merb-cache/stores/strategy/action_store.rb +56 -0
  14. data/lib/merb-cache/stores/strategy/adhoc_store.rb +69 -0
  15. data/lib/merb-cache/stores/strategy/gzip_store.rb +63 -0
  16. data/lib/merb-cache/stores/strategy/page_store.rb +64 -0
  17. data/lib/merb-cache/stores/strategy/sha1_store.rb +62 -0
  18. data/lib/merb-cache.rb +8 -7
  19. data/spec/merb-cache/cache_spec.rb +88 -0
  20. data/spec/merb-cache/core_ext/enumerable_spec.rb +22 -0
  21. data/spec/merb-cache/core_ext/hash_spec.rb +20 -0
  22. data/spec/merb-cache/merb_ext/controller_spec.rb +284 -0
  23. data/spec/merb-cache/stores/fundamental/abstract_store_spec.rb +166 -0
  24. data/spec/merb-cache/stores/fundamental/file_store_spec.rb +186 -0
  25. data/spec/merb-cache/stores/fundamental/memcached_store_spec.rb +243 -0
  26. data/spec/merb-cache/stores/strategy/abstract_strategy_store_spec.rb +78 -0
  27. data/spec/merb-cache/stores/strategy/action_store_spec.rb +189 -0
  28. data/spec/merb-cache/stores/strategy/adhoc_store_spec.rb +225 -0
  29. data/spec/merb-cache/stores/strategy/gzip_store_spec.rb +51 -0
  30. data/spec/merb-cache/stores/strategy/page_store_spec.rb +111 -0
  31. data/spec/merb-cache/stores/strategy/sha1_store_spec.rb +75 -0
  32. data/spec/spec_helper.rb +69 -72
  33. metadata +42 -31
  34. data/lib/merb-cache/cache-action.rb +0 -144
  35. data/lib/merb-cache/cache-fragment.rb +0 -95
  36. data/lib/merb-cache/cache-page.rb +0 -203
  37. data/lib/merb-cache/cache-store/database-activerecord.rb +0 -88
  38. data/lib/merb-cache/cache-store/database-datamapper.rb +0 -79
  39. data/lib/merb-cache/cache-store/database-sequel.rb +0 -78
  40. data/lib/merb-cache/cache-store/database.rb +0 -144
  41. data/lib/merb-cache/cache-store/dummy.rb +0 -106
  42. data/lib/merb-cache/cache-store/file.rb +0 -194
  43. data/lib/merb-cache/cache-store/memcache.rb +0 -199
  44. data/lib/merb-cache/cache-store/memory.rb +0 -168
  45. data/lib/merb-cache/merb-cache.rb +0 -165
  46. data/lib/merb-cache/merbtasks.rb +0 -6
  47. data/spec/config/database.yml +0 -14
  48. data/spec/controller.rb +0 -101
  49. data/spec/log/merb_test.log +0 -433
  50. data/spec/merb-cache-action_spec.rb +0 -162
  51. data/spec/merb-cache-fragment_spec.rb +0 -100
  52. data/spec/merb-cache-page_spec.rb +0 -150
  53. data/spec/merb-cache_spec.rb +0 -15
  54. data/spec/views/cache_controller/action1.html.erb +0 -4
  55. data/spec/views/cache_controller/action2.html.haml +0 -4
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008 Alex Boussinet
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
- = merb-cache
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
- Implemented cache stores:
9
+ Tutorial
10
+ ==========
13
11
 
14
- - memory
15
- - memcache
16
- - file
17
- - database (sequel, datamapper, activerecord)
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
- == Quick intro
20
- With fragment caching, you can mix dynamic and static content.
17
+ # create a fundamental memcache store named :memcached for localhost
21
18
 
22
- With action caching, the whole template is cached
23
- but the before filters are still processed.
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
- def some_action_that_invalidates_cache
130
- expire_page(:action_name)
131
- expire_action(:another_action)
132
- render
133
- end
23
+ # a default FileStore
24
+ Merb::Cache.setup do
25
+ register(FileStore)
26
+ end
134
27
 
135
- def delete
136
- expire("active_users")
137
- render
138
- end
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
- def archives
141
- @archives = User.archives unless cached?("users_archives")
142
- render
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
- def index
146
- render
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
- ====views/users/archive.html.erb
158
- <%- cache "users_archives" do %>
159
- <div>some big template</div>
160
- <% end -%>
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 &lt; 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 = "Alex Boussinet"
15
- GEM_EMAIL = "alex.boussinet@gmail.com"
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.7") + PKG_BUILD
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.7')
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 => [:package] do
48
- sh install_command(GEM_NAME, GEM_VERSION)
48
+ task :install do
49
+ Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
49
50
  end
50
51
 
51
- namespace :jruby do
52
+ desc "Uninstall the gem"
53
+ task :uninstall do
54
+ Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
55
+ end
52
56
 
53
- desc "Run :package and install the resulting .gem with jruby"
54
- task :install => :package do
55
- sh jinstall_command(GEM_NAME, GEM_VERSION)
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
@@ -1,2 +0,0 @@
1
- capture_#{engine} and concat_#{engine} are required for cache()
2
- implement other cache store ?
@@ -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,9 @@
1
+ module Enumerable
2
+ def capture_first
3
+ each do |o|
4
+ return yield(o) || next
5
+ end
6
+
7
+ nil
8
+ end
9
+ end
@@ -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