mongo_store 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +86 -0
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/active_support/cache/mongo_store.rb +115 -0
- data/lib/mongo_store.rb +2 -72
- data/mongo_store.gemspec +65 -0
- data/spec/active_support/cache/mongo_store_spec.rb +187 -0
- data/spec/spec_helper.rb +1 -0
- metadata +10 -8
- data/README.rdoc +0 -17
- data/spec/mongo_store_spec.rb +0 -120
data/README.markdown
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# MongoStore
|
2
|
+
|
3
|
+
It's **ActiveSupport::Cache::MongoStore** -- a [MongoDB](http://mongodb.org)-based provider for the standard Rails cache mechanism. With an emphasis on fast writes and a memory-mapped architecture, Mongo is well-suited to caching. This gem aims to give you what the ubiquitous **MemCacheStore** does, but with Mongo's persistence. (And without having to put a second RAM devourer in an environment already running Mongo.)
|
4
|
+
|
5
|
+
## Getting Started
|
6
|
+
|
7
|
+
The only prerequisites are [ActiveSupport](http://rubygems.org/gems/activesupport) and the [mongo](http://rubygems.org/gems/mongo) gem. If your app uses [MongoMapper](htp://rubygems.org/gems/mongo_mapper) we can detect it and use the same database connection, but we don't require it.
|
8
|
+
|
9
|
+
$ gem install mongo_store # or 'sudo' as required
|
10
|
+
|
11
|
+
In your Rails application, just configure your **config/environments/production.rb** (and/or **staging.rb** and any other environment you want to cache) like so:
|
12
|
+
|
13
|
+
config.cache_store = :mongo_store
|
14
|
+
|
15
|
+
This default behavior creates a collection called **rails\_cache** in either the Mongo database referenced by `MongoMapper.database` (if you're using MM) or a database also called **rails\_cache**. You can override these:
|
16
|
+
|
17
|
+
config.cache_store = :mongo_store, "foo" # Collection name is "foo"
|
18
|
+
config.cache_store = :mongo_store, "foo", :db => "bar" # DB name is "bar"
|
19
|
+
|
20
|
+
# You can pass a DB object instead of a database name...
|
21
|
+
deebee = Mongo::DB.new("bar", Mongo::Connection.new)
|
22
|
+
config.cache_store = :mongo_store, "foo", :db => deebee
|
23
|
+
|
24
|
+
# Or just pass in a Collection object and you're covered...
|
25
|
+
collie = Mongo::Collection.new(deebee, "foo")
|
26
|
+
config.cache_store = :mongo_store, collie
|
27
|
+
|
28
|
+
We don't have a separate option for connecting to a different server. If you don't intend to use localhost, make a new Mongo::Collection or Mongo::DB object from a different connection.
|
29
|
+
|
30
|
+
## Options
|
31
|
+
|
32
|
+
The following hash options are recognized on initialization:
|
33
|
+
|
34
|
+
* `:db` - A Mongo::DB object or the name of one to create. Defaults to **rails_cache**.
|
35
|
+
* `:create_index` - By default, we create an index on the _key_ and _expires_ fields. Set to **false** to override.
|
36
|
+
* `:expires_in` - The global length of time a cache key remains valid. Defaults to 1 year. Can also be set on individual cache writes.
|
37
|
+
|
38
|
+
## Other Goodness
|
39
|
+
|
40
|
+
MongoStore is a drop-in caching store and doesn't require any special treatment. The only extra behavior on top of what you get from ActiveSupport::Cache::Store is as follows:
|
41
|
+
|
42
|
+
### :expires_in option on the #write method
|
43
|
+
This is the same behavior as the option in the MemCacheStore. Specify a number of seconds or an ActiveSupport helper equivalent, e.g.: `:expires_in => 5.minutes`. Keys past their expiration date are not returned on reads.
|
44
|
+
|
45
|
+
_**NOTE:** This behavior is fairly dumb and uses `Time.now` on the application side. If you have a number of app servers hitting one database and their times aren't in sync, expect unwarranted cache misses._
|
46
|
+
|
47
|
+
### #clean_expired method
|
48
|
+
If the collection size starts to explode from old cached values that are never being written again, you can set up a delayed job or Rake task to run `Rails.cache.clean_expired` every few weeks or such. Cached values that _are_ reused are updated in place, so running this too frequently may actually impair performance.
|
49
|
+
|
50
|
+
### #clear method
|
51
|
+
Empties the cache. The moral equivalent of `Rails.cache.delete_matched(/.*/)` but faster.
|
52
|
+
|
53
|
+
## Limitations
|
54
|
+
|
55
|
+
* Keys and values must be valid Mongo types. In practice, caching seems mostly to be used on strings, so this probably doesn't hurt you. Just be aware that no attempt is made to serialize complex Ruby objects. That's what ORMs are for. (See my [Candy](http://rubygems.org/gems/candy) gem for some work I've been doing in this direction, however.)
|
56
|
+
|
57
|
+
* Upserts and atomic operators are used for performance and simplicity. Writing to an existing key will change the value and expiration date. This is fast, but expired keys that are never written again _will_ keep hanging around until you delete them explicitly or run `#clean_expired`. This may or may not matter depending on how numerous and reusable your keys are. For typical Rails app use cases, you're probably fine. If you really care about every byte of disk space, you probably ought to reconsider using MongoDB anyway.
|
58
|
+
|
59
|
+
* Mongo documents have a size limit of 4 MB. No attempt is made to work around this. If you're trying to trying to stuff anything larger than that into Rails caching, you're on your own.
|
60
|
+
|
61
|
+
* Do not use a capped collection. Doing so will prevent deletes from deleting, some updates from updating, and the trees and flowers from growing in the Spring.
|
62
|
+
|
63
|
+
* This code and specs were written for Ruby 1.9.1. I tried to make sure it works fine for you anachronists (Ruby 1.8), but didn't confirm it. Please register an issue if I forgot your quaint historical syntax. I also didn't test it in JRuby, Rubinius, IronRuby, or your roommate's HP-48 calculator.
|
64
|
+
|
65
|
+
* Likewise, this was written for Rails 2.3.5. I have not tried to run it in Rails 3 yet, which is still in beta at the time of this writing. I don't even know if Rails 3 uses the same caching approach. It probably caches using pure energy or something, and removes carbon from the atmosphere at the same time.
|
66
|
+
|
67
|
+
* I am optimistic about performance but have not benchmarked it, apart from "It's faster than not using a cache."
|
68
|
+
|
69
|
+
## Support
|
70
|
+
|
71
|
+
You can find the docs here:
|
72
|
+
http://rdoc.info/projects/SFEley/mongo_store/
|
73
|
+
|
74
|
+
Other than that, there is no email list, forum, wiki, Google Wave, or international convention for this gem. Come on. It's a hundred lines of code.
|
75
|
+
|
76
|
+
## Contributing
|
77
|
+
|
78
|
+
Please leave an issue. Or fork, fix, and pull-request. Or send me an email (sfeley@gmail.com). Or buy me a whisky at the hotel bar. (Single malt only, please.)
|
79
|
+
|
80
|
+
You can also [check out my podcast](http://escapepod.org) if you like science fiction stories.
|
81
|
+
|
82
|
+
And Have Fun.
|
83
|
+
|
84
|
+
## Copyright
|
85
|
+
|
86
|
+
Copyright (c) 2010 Stephen Eley. See LICENSE for details.
|
data/Rakefile
CHANGED
@@ -5,8 +5,8 @@ begin
|
|
5
5
|
require 'jeweler'
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "mongo_store"
|
8
|
-
gem.summary = %Q{
|
9
|
-
gem.description = %Q{
|
8
|
+
gem.summary = %Q{Rails caching for MongoDB}
|
9
|
+
gem.description = %Q{It's ActiveSupport::Cache::MongoStore -- a MongoDB-based provider for the standard Rails cache mechanism. With an emphasis on fast writes and a memory-mapped architecture, Mongo is well-suited to caching. This gem aims to give you what the ubiquitous MemCacheStore does, but with Mongo's persistence. (And without having to put a second RAM devourer in an environment already running Mongo.)}
|
10
10
|
gem.email = "sfeley@gmail.com"
|
11
11
|
gem.homepage = "http://github.com/SFEley/mongo_store"
|
12
12
|
gem.authors = ["Stephen Eley"]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'mongo'
|
3
|
+
|
4
|
+
module ActiveSupport
|
5
|
+
module Cache
|
6
|
+
class MongoStore < Store
|
7
|
+
attr_reader :options
|
8
|
+
attr_accessor :expires_in
|
9
|
+
|
10
|
+
# Returns a MongoDB cache store. Can take either a Mongo::Collection object or a collection name.
|
11
|
+
# If neither is provided, a collection named "rails_cache" is created.
|
12
|
+
#
|
13
|
+
# An options hash may also be provided with the following options:
|
14
|
+
#
|
15
|
+
# * :expires_in - The default expiration period for cached objects. If not provided, defaults to 1 year.
|
16
|
+
# * :db - Either a Mongo::DB object or a database name. Not used if a Mongo::Collection object is passed. Otherwise defaults to MongoMapper.database (if MongoMapper is used in the app) or else creates a DB named "rails_cache".
|
17
|
+
# * :create_index - Whether to index the key and expiration date on the collection. Defaults to true. Not used if a Mongo::Collection object is passed.
|
18
|
+
def initialize(collection = nil, options = nil)
|
19
|
+
@options = {
|
20
|
+
:collection_name => 'rails_cache',
|
21
|
+
:db_name => 'rails_cache',
|
22
|
+
:expires_in => 1.year,
|
23
|
+
:create_index => true
|
24
|
+
}
|
25
|
+
# @options.merge!(options) if options
|
26
|
+
case collection
|
27
|
+
when Mongo::Collection
|
28
|
+
@collection = collection
|
29
|
+
when String
|
30
|
+
@options[:collection_name] = collection
|
31
|
+
when Hash
|
32
|
+
@options.merge!(collection)
|
33
|
+
when nil
|
34
|
+
# No op
|
35
|
+
else
|
36
|
+
raise TypeError, "MongoStore parameters must be a Mongo::Collection, a collection name, and/or an options hash."
|
37
|
+
end
|
38
|
+
|
39
|
+
@options.merge!(options) if options.is_a?(Hash)
|
40
|
+
|
41
|
+
# Set the expiration time
|
42
|
+
self.expires_in = @options[:expires_in]
|
43
|
+
super()
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the MongoDB collection described in the options to .new (or else the default 'rails_cache' one.)
|
47
|
+
# Lazily creates the object on first access so that we can look for a MongoMapper database _after_
|
48
|
+
# MongoMapper initializes.
|
49
|
+
def collection
|
50
|
+
@collection ||= make_collection
|
51
|
+
end
|
52
|
+
|
53
|
+
# Removes old cached values that have expired. Set this up to run occasionally in delayed_job, etc., if you
|
54
|
+
# start worrying about space. (In practice, because we favor updating over inserting, space is only wasted
|
55
|
+
# if the key itself never gets cached again. It also means you can _reduce_ efficiency by running this
|
56
|
+
# too often.)
|
57
|
+
def clean_expired
|
58
|
+
collection.remove({'expires' => {'$lt' => Time.now}})
|
59
|
+
end
|
60
|
+
|
61
|
+
# Wipes the whole cache.
|
62
|
+
def clear
|
63
|
+
collection.remove
|
64
|
+
end
|
65
|
+
|
66
|
+
# Inserts the value into the cache collection or updates the existing value. The value must be a valid
|
67
|
+
# MongoDB type. An *:expires_in* option may be provided, as with MemCacheStore. If one is _not_
|
68
|
+
# provided, a default expiration of 1 year is used.
|
69
|
+
def write(key, value, options=nil)
|
70
|
+
super
|
71
|
+
expires = Time.now + ((options && options[:expires_in]) || expires_in)
|
72
|
+
collection.update({'key' => key}, {'$set' => {'value' => value, 'expires' => expires}}, :upsert => true)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Reads the value from the cache collection.
|
76
|
+
def read(key, options=nil)
|
77
|
+
super
|
78
|
+
if doc = collection.find_one('key' => key, 'expires' => {'$gt' => Time.now})
|
79
|
+
doc['value']
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Takes the specified value out of the collection.
|
84
|
+
def delete(key, options=nil)
|
85
|
+
super
|
86
|
+
collection.remove({'key' => key})
|
87
|
+
end
|
88
|
+
|
89
|
+
# With MongoDB, there's no difference between querying on an exact value or a regex. Beautiful, huh?
|
90
|
+
alias_method :delete_matched, :delete
|
91
|
+
|
92
|
+
private
|
93
|
+
def mongomapper?
|
94
|
+
Kernel.const_defined?(:MongoMapper) && MongoMapper.respond_to?(:database) && MongoMapper.database
|
95
|
+
end
|
96
|
+
|
97
|
+
def make_collection
|
98
|
+
db = case options[:db]
|
99
|
+
when Mongo::DB then options[:db]
|
100
|
+
when String then Mongo::DB.new(options[:db], Mongo::Connection.new)
|
101
|
+
else
|
102
|
+
if mongomapper?
|
103
|
+
MongoMapper.database
|
104
|
+
else
|
105
|
+
Mongo::DB.new(options[:db_name], Mongo::Connection.new)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
coll = db.create_collection(options[:collection_name])
|
109
|
+
coll.create_index('key' => Mongo::ASCENDING, 'expires' => Mongo::DESCENDING) if options[:create_index]
|
110
|
+
coll
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/mongo_store.rb
CHANGED
@@ -1,77 +1,7 @@
|
|
1
1
|
require 'active_support'
|
2
|
-
require 'mongo'
|
3
2
|
|
4
3
|
module ActiveSupport
|
5
4
|
module Cache
|
6
|
-
|
7
|
-
attr_reader :collection
|
8
|
-
|
9
|
-
# Returns a MongoDB cache store. Takes several possible combinations of parameters. In order of
|
10
|
-
# escalating guesswork:
|
11
|
-
#
|
12
|
-
# 1. *a Mongo::Collection object* - No guessing. The collection is used as the cache store.
|
13
|
-
# 2. *a collection name and a database name* - Mongo objects are created for both. The default 'localhost:27017' connection is used.
|
14
|
-
# 3. *a collection name* - Uses either MongoMapper.database (if MongoMapper is defined in the app) or a DB with the same name.
|
15
|
-
# 4. *no parameters* - A collection named 'rails_cache' is created, using either MongoMapper.database (if MongoMapper is defined in the app) or a DB also named 'rails_cache'.
|
16
|
-
#
|
17
|
-
# Unless option 1 is used, indexes are created on the key and expiration fields. If you supply your own collection,
|
18
|
-
# you are also responsible for making your own indexes.
|
19
|
-
def initialize(collection = nil, db_name = nil)
|
20
|
-
@collection = case collection
|
21
|
-
when Mongo::Collection then collection
|
22
|
-
when String
|
23
|
-
make_collection(collection, db_name)
|
24
|
-
when nil
|
25
|
-
make_collection('rails_cache')
|
26
|
-
else
|
27
|
-
raise TypeError, "MongoStore parameters must be nil, a Mongo::Collection, or a collection name."
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
# Inserts the value into the cache collection or updates the existing value. The value must be a valid
|
32
|
-
# MongoDB type. An *:expires_in* option may be provided, as with MemCacheStore. If one is _not_
|
33
|
-
# provided, a default expiration of 1 year is used.
|
34
|
-
def write(key, value, options={})
|
35
|
-
super
|
36
|
-
expires = Time.now + (options[:expires_in] || 1.year)
|
37
|
-
collection.update({'key' => key}, {'$set' => {'value' => value, 'expires' => expires}}, :upsert => true)
|
38
|
-
end
|
39
|
-
|
40
|
-
# Reads the value from the cache collection.
|
41
|
-
def read(key, options={})
|
42
|
-
super
|
43
|
-
if doc = collection.find_one('key' => key, 'expires' => {'$gt' => Time.now})
|
44
|
-
doc['value']
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
# Takes the specified value out of the collection.
|
49
|
-
def delete(key, options={})
|
50
|
-
super
|
51
|
-
collection.remove({'key' => key})
|
52
|
-
end
|
53
|
-
|
54
|
-
# With MongoDB, there's no difference between querying on an exact value or a regex. Beautiful, huh?
|
55
|
-
alias_method :delete_matched, :delete
|
56
|
-
|
57
|
-
private
|
58
|
-
def mongomapper?
|
59
|
-
Kernel.const_defined?(:MongoMapper) && MongoMapper.respond_to?(:database) && MongoMapper.database
|
60
|
-
end
|
61
|
-
|
62
|
-
def make_collection(collection, db_name=nil)
|
63
|
-
if db_name
|
64
|
-
db = Mongo::DB.new(db_name, Mongo::Connection.new)
|
65
|
-
elsif mongomapper?
|
66
|
-
db = MongoMapper.database
|
67
|
-
else
|
68
|
-
db = Mongo::DB.new(collection, Mongo::Connection.new)
|
69
|
-
end
|
70
|
-
coll = db.create_collection(collection)
|
71
|
-
coll.create_index('key' => Mongo::ASCENDING, 'expires' => Mongo::DESCENDING)
|
72
|
-
coll
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
5
|
+
autoload :MongoStore, 'active_support/cache/mongo_store'
|
76
6
|
end
|
77
|
-
end
|
7
|
+
end
|
data/mongo_store.gemspec
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{mongo_store}
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Stephen Eley"]
|
12
|
+
s.date = %q{2010-03-06}
|
13
|
+
s.description = %q{It's ActiveSupport::Cache::MongoStore -- a MongoDB-based provider for the standard Rails cache mechanism. With an emphasis on fast writes and a memory-mapped architecture, Mongo is well-suited to caching. This gem aims to give you what the ubiquitous MemCacheStore does, but with Mongo's persistence. (And without having to put a second RAM devourer in an environment already running Mongo.)}
|
14
|
+
s.email = %q{sfeley@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.markdown",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"lib/active_support/cache/mongo_store.rb",
|
27
|
+
"lib/mongo_store.rb",
|
28
|
+
"mongo_store.gemspec",
|
29
|
+
"spec/active_support/cache/mongo_store_spec.rb",
|
30
|
+
"spec/spec.opts",
|
31
|
+
"spec/spec_helper.rb"
|
32
|
+
]
|
33
|
+
s.homepage = %q{http://github.com/SFEley/mongo_store}
|
34
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
35
|
+
s.require_paths = ["lib"]
|
36
|
+
s.rubygems_version = %q{1.3.6}
|
37
|
+
s.summary = %q{Rails caching for MongoDB}
|
38
|
+
s.test_files = [
|
39
|
+
"spec/active_support/cache/mongo_store_spec.rb",
|
40
|
+
"spec/spec_helper.rb"
|
41
|
+
]
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
45
|
+
s.specification_version = 3
|
46
|
+
|
47
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
48
|
+
s.add_runtime_dependency(%q<mongo>, [">= 0.18.3"])
|
49
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 2.2"])
|
50
|
+
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
51
|
+
s.add_development_dependency(%q<mocha>, [">= 0.9"])
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<mongo>, [">= 0.18.3"])
|
54
|
+
s.add_dependency(%q<activesupport>, [">= 2.2"])
|
55
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
56
|
+
s.add_dependency(%q<mocha>, [">= 0.9"])
|
57
|
+
end
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<mongo>, [">= 0.18.3"])
|
60
|
+
s.add_dependency(%q<activesupport>, [">= 2.2"])
|
61
|
+
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
62
|
+
s.add_dependency(%q<mocha>, [">= 0.9"])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
|
2
|
+
|
3
|
+
# Stubbing MongoMapper out so that we don't have to have it installed for testing
|
4
|
+
class MongoMapper
|
5
|
+
end
|
6
|
+
|
7
|
+
module ActiveSupport
|
8
|
+
module Cache
|
9
|
+
describe MongoStore do
|
10
|
+
describe "initializing" do
|
11
|
+
it "can take a Mongo::Collection object" do
|
12
|
+
db = Mongo::DB.new('mongo_store_test', Mongo::Connection.new)
|
13
|
+
coll = Mongo::Collection.new(db, 'foostore')
|
14
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store, coll)
|
15
|
+
store.collection.should == coll
|
16
|
+
end
|
17
|
+
|
18
|
+
it "can take a collection name" do
|
19
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store, 'foo')
|
20
|
+
store.collection.name.should == 'foo'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "defaults the collection name to 'rails_cache'" do
|
24
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store)
|
25
|
+
store.collection.name.should == 'rails_cache'
|
26
|
+
end
|
27
|
+
|
28
|
+
it "can take a Mongo::DB object for a :db option" do
|
29
|
+
deebee = Mongo::DB.new('mongo_store_test_deebee', Mongo::Connection.new)
|
30
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store, :db => deebee)
|
31
|
+
store.collection.db.should == deebee
|
32
|
+
end
|
33
|
+
|
34
|
+
it "can take a database name for a :db option" do
|
35
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store, :db => 'mongo_store_test_name')
|
36
|
+
store.collection.db.name.should == 'mongo_store_test_name'
|
37
|
+
end
|
38
|
+
|
39
|
+
it "uses MongoMapper if no other DB is provided" do
|
40
|
+
mappy = Mongo::DB.new('mongo_store_test_mappy', Mongo::Connection.new)
|
41
|
+
MongoMapper.expects(:database).at_least_once.returns(mappy)
|
42
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store)
|
43
|
+
store.collection.db.should == mappy
|
44
|
+
end
|
45
|
+
|
46
|
+
it "lazy loads so that MongoMapper can be initialized first" do
|
47
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store)
|
48
|
+
# Notice the order! It's what differentiates this test from the above.
|
49
|
+
lazy = Mongo::DB.new('mongo_store_test_lazy', Mongo::Connection.new)
|
50
|
+
MongoMapper.expects(:database).at_least_once.returns(lazy)
|
51
|
+
store.collection.db.should == lazy
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
it "defaults the database name to 'rails_cache'" do
|
56
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store)
|
57
|
+
store.collection.db.name.should == 'rails_cache'
|
58
|
+
end
|
59
|
+
|
60
|
+
it "defaults to creating an index" do
|
61
|
+
Mongo::Collection.any_instance.expects(:create_index)
|
62
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store)
|
63
|
+
store.collection.should_not be_nil
|
64
|
+
end
|
65
|
+
|
66
|
+
it "can turn off index creation" do
|
67
|
+
Mongo::Collection.any_instance.expects(:create_index).never
|
68
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store, :create_index => false)
|
69
|
+
store.collection.should_not be_nil
|
70
|
+
end
|
71
|
+
|
72
|
+
it "defaults to an expiration of 1 year" do
|
73
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store)
|
74
|
+
store.expires_in.should == 1.year
|
75
|
+
end
|
76
|
+
|
77
|
+
it "can override expiration time with the :expires_in option" do
|
78
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store, :expires_in => 1.week)
|
79
|
+
store.expires_in.should == 1.week
|
80
|
+
end
|
81
|
+
|
82
|
+
it "can take options as the first parameter" do
|
83
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store, :expires_in => 1.minute)
|
84
|
+
store.expires_in.should == 1.minute
|
85
|
+
end
|
86
|
+
|
87
|
+
it "can take options as the second parameter" do
|
88
|
+
store = ActiveSupport::Cache.lookup_store(:mongo_store, 'foo', :expires_in => 1.day)
|
89
|
+
store.expires_in.should == 1.day
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
after(:all) do
|
94
|
+
c = Mongo::Connection.new
|
95
|
+
%w(rails_cache mongo_store_test_name mongo_store_test_deebee mongo_store_test_mappy mongo_store_test_lazy).each do |db|
|
96
|
+
c.drop_database(db)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "caching" do
|
102
|
+
before(:all) do
|
103
|
+
@store = ActiveSupport::Cache.lookup_store(:mongo_store, 'mongo_store_test', :db => 'mongo_store_test')
|
104
|
+
end
|
105
|
+
|
106
|
+
it "can write values" do
|
107
|
+
@store.write('fnord', 'I am vaguely disturbed.')
|
108
|
+
@store.collection.find_one(:key => 'fnord')['value'].should == "I am vaguely disturbed."
|
109
|
+
end
|
110
|
+
|
111
|
+
it "can read values" do
|
112
|
+
@store.collection.insert({:key => 'yoo', :value => 'yar', :expires => 1.year.from_now})
|
113
|
+
@store.read('yoo').should == 'yar'
|
114
|
+
end
|
115
|
+
|
116
|
+
it "can delete keys" do
|
117
|
+
@store.write('foo', 'bar')
|
118
|
+
@store.read('foo').should == 'bar'
|
119
|
+
@store.delete('foo')
|
120
|
+
@store.read('foo').should be_nil
|
121
|
+
end
|
122
|
+
|
123
|
+
it "can delete keys matching a regular expression" do
|
124
|
+
@store.write('foo', 'bar')
|
125
|
+
@store.write('fodder', 'bother')
|
126
|
+
@store.write('yoo', 'yar')
|
127
|
+
# Initial state
|
128
|
+
@store.read('foo').should == 'bar'
|
129
|
+
@store.read('fodder').should == 'bother'
|
130
|
+
@store.read('yoo').should == 'yar'
|
131
|
+
# The work
|
132
|
+
@store.delete_matched /oo/
|
133
|
+
# Post state
|
134
|
+
@store.read('foo').should be_nil
|
135
|
+
@store.read('fodder').should == 'bother'
|
136
|
+
@store.read('yoo').should be_nil
|
137
|
+
end
|
138
|
+
|
139
|
+
it "can expire a value with the :expires_in option" do
|
140
|
+
@store.write('ray', 'dar', :expires_in => 2.seconds)
|
141
|
+
@store.read('ray').should == 'dar'
|
142
|
+
sleep(3)
|
143
|
+
@store.read('ray').should be_nil
|
144
|
+
end
|
145
|
+
|
146
|
+
it "can expire a value from the global setting" do
|
147
|
+
old_expires = @store.expires_in
|
148
|
+
@store.expires_in = 2.seconds
|
149
|
+
@store.write('foo', 'bar')
|
150
|
+
@store.write('yoo', 'yar', :expires_in => 5.minutes)
|
151
|
+
@store.read('foo').should == 'bar'
|
152
|
+
@store.read('yoo').should == 'yar'
|
153
|
+
sleep(3)
|
154
|
+
@store.read('foo').should be_nil
|
155
|
+
@store.read('yoo').should == 'yar'
|
156
|
+
@store.expires_in = old_expires
|
157
|
+
end
|
158
|
+
|
159
|
+
it "can clean up expired values" do
|
160
|
+
@store.write('foo', 'bar', :expires_in => 2.seconds)
|
161
|
+
@store.write('yoo', 'yar', :expires_in => 2.days)
|
162
|
+
sleep(3)
|
163
|
+
@store.collection.count.should == 2
|
164
|
+
@store.clean_expired
|
165
|
+
@store.collection.count.should == 1
|
166
|
+
end
|
167
|
+
|
168
|
+
it "can clear the whole cache" do
|
169
|
+
@store.write('foo', 'bar')
|
170
|
+
@store.write('yoo', 'yar', :expires_in => 2.days)
|
171
|
+
@store.collection.count.should == 2
|
172
|
+
@store.clear
|
173
|
+
@store.collection.count.should == 0
|
174
|
+
end
|
175
|
+
|
176
|
+
after(:each) do
|
177
|
+
@store.collection.remove # Clear our records
|
178
|
+
end
|
179
|
+
|
180
|
+
after(:all) do
|
181
|
+
c = Mongo::Connection.new
|
182
|
+
c.drop_database('mongo_store_test')
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 2
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.2.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Stephen Eley
|
@@ -71,7 +71,7 @@ dependencies:
|
|
71
71
|
version: "0.9"
|
72
72
|
type: :development
|
73
73
|
version_requirements: *id004
|
74
|
-
description:
|
74
|
+
description: It's ActiveSupport::Cache::MongoStore -- a MongoDB-based provider for the standard Rails cache mechanism. With an emphasis on fast writes and a memory-mapped architecture, Mongo is well-suited to caching. This gem aims to give you what the ubiquitous MemCacheStore does, but with Mongo's persistence. (And without having to put a second RAM devourer in an environment already running Mongo.)
|
75
75
|
email: sfeley@gmail.com
|
76
76
|
executables: []
|
77
77
|
|
@@ -79,16 +79,18 @@ extensions: []
|
|
79
79
|
|
80
80
|
extra_rdoc_files:
|
81
81
|
- LICENSE
|
82
|
-
- README.
|
82
|
+
- README.markdown
|
83
83
|
files:
|
84
84
|
- .document
|
85
85
|
- .gitignore
|
86
86
|
- LICENSE
|
87
|
-
- README.
|
87
|
+
- README.markdown
|
88
88
|
- Rakefile
|
89
89
|
- VERSION
|
90
|
+
- lib/active_support/cache/mongo_store.rb
|
90
91
|
- lib/mongo_store.rb
|
91
|
-
-
|
92
|
+
- mongo_store.gemspec
|
93
|
+
- spec/active_support/cache/mongo_store_spec.rb
|
92
94
|
- spec/spec.opts
|
93
95
|
- spec/spec_helper.rb
|
94
96
|
has_rdoc: true
|
@@ -120,7 +122,7 @@ rubyforge_project:
|
|
120
122
|
rubygems_version: 1.3.6
|
121
123
|
signing_key:
|
122
124
|
specification_version: 3
|
123
|
-
summary:
|
125
|
+
summary: Rails caching for MongoDB
|
124
126
|
test_files:
|
125
|
-
- spec/mongo_store_spec.rb
|
127
|
+
- spec/active_support/cache/mongo_store_spec.rb
|
126
128
|
- spec/spec_helper.rb
|
data/README.rdoc
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
= MongoStore
|
2
|
-
|
3
|
-
It's **ActiveSupport::Cache::MongoStore** -- if you know your Rails caching, _'nuff said._
|
4
|
-
|
5
|
-
== Note on Patches/Pull Requests
|
6
|
-
|
7
|
-
* Fork the project.
|
8
|
-
* Make your feature addition or bug fix.
|
9
|
-
* Add tests for it. This is important so I don't break it in a
|
10
|
-
future version unintentionally.
|
11
|
-
* Commit, do not mess with rakefile, version, or history.
|
12
|
-
(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)
|
13
|
-
* Send me a pull request. Bonus points for topic branches.
|
14
|
-
|
15
|
-
== Copyright
|
16
|
-
|
17
|
-
Copyright (c) 2010 Stephen Eley. See LICENSE for details.
|
data/spec/mongo_store_spec.rb
DELETED
@@ -1,120 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
-
|
3
|
-
# Stubbing MongoMapper out so that we don't have to have it installed for testing
|
4
|
-
class MongoMapper
|
5
|
-
end
|
6
|
-
|
7
|
-
describe "MongoStore" do
|
8
|
-
describe "collection" do
|
9
|
-
it "can be specified as a Mongo::Collection object" do
|
10
|
-
db = Mongo::DB.new('mongo_store_test', Mongo::Connection.new)
|
11
|
-
coll = Mongo::Collection.new(db, 'foostore')
|
12
|
-
store = ActiveSupport::Cache.lookup_store(:mongo_store, coll)
|
13
|
-
store.collection.should == coll
|
14
|
-
end
|
15
|
-
|
16
|
-
it "can take a collection name and a database name" do
|
17
|
-
store = ActiveSupport::Cache.lookup_store(:mongo_store, 'foo', 'bar')
|
18
|
-
store.collection.name.should == 'foo'
|
19
|
-
store.collection.db.name.should == 'bar'
|
20
|
-
end
|
21
|
-
|
22
|
-
describe "with MongoMapper" do
|
23
|
-
before(:each) do
|
24
|
-
db = Mongo::DB.new('mappy', Mongo::Connection.new)
|
25
|
-
MongoMapper.expects(:database).twice.returns(db)
|
26
|
-
end
|
27
|
-
|
28
|
-
it "can take a collection name" do
|
29
|
-
store = ActiveSupport::Cache.lookup_store(:mongo_store, 'happy')
|
30
|
-
store.collection.name.should == 'happy'
|
31
|
-
store.collection.db.name.should == 'mappy'
|
32
|
-
end
|
33
|
-
|
34
|
-
it "defaults to a 'rails_cache' collection" do
|
35
|
-
store = ActiveSupport::Cache.lookup_store(:mongo_store)
|
36
|
-
store.collection.name.should == 'rails_cache'
|
37
|
-
store.collection.db.name.should == 'mappy'
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
describe "without MongoMapper" do
|
42
|
-
it "can take a collection name" do
|
43
|
-
store = ActiveSupport::Cache.lookup_store(:mongo_store, 'yuna')
|
44
|
-
store.collection.name.should == 'yuna'
|
45
|
-
store.collection.db.name.should == 'yuna'
|
46
|
-
end
|
47
|
-
|
48
|
-
it "defaults to a 'rails_cache' collection" do
|
49
|
-
store = ActiveSupport::Cache.lookup_store(:mongo_store)
|
50
|
-
store.collection.name.should == 'rails_cache'
|
51
|
-
store.collection.db.name.should == 'rails_cache'
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
it "raises an exception if an unusable parameter is passed" do
|
56
|
-
lambda{ActiveSupport::Cache.lookup_store(:mongo_store, 5)}.should raise_error(TypeError)
|
57
|
-
end
|
58
|
-
|
59
|
-
after(:all) do
|
60
|
-
c = Mongo::Connection.new
|
61
|
-
%w(bar mappy rails_cache yuna).each do |db|
|
62
|
-
c.drop_database(db)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
describe "caching" do
|
68
|
-
before(:all) do
|
69
|
-
@store = ActiveSupport::Cache.lookup_store(:mongo_store, 'mongo_store_test')
|
70
|
-
end
|
71
|
-
|
72
|
-
it "can write values" do
|
73
|
-
@store.write('fnord', 'I am vaguely disturbed.')
|
74
|
-
@store.collection.find_one(:key => 'fnord')['value'].should == "I am vaguely disturbed."
|
75
|
-
end
|
76
|
-
|
77
|
-
it "can read values" do
|
78
|
-
@store.collection.insert({:key => 'yoo', :value => 'yar', :expires => 1.year.from_now})
|
79
|
-
@store.read('yoo').should == 'yar'
|
80
|
-
end
|
81
|
-
|
82
|
-
it "can delete keys" do
|
83
|
-
@store.write('foo', 'bar')
|
84
|
-
@store.read('foo').should == 'bar'
|
85
|
-
@store.delete('foo')
|
86
|
-
@store.read('foo').should be_nil
|
87
|
-
end
|
88
|
-
|
89
|
-
it "can delete keys matching a regular expression" do
|
90
|
-
@store.write('foo', 'bar')
|
91
|
-
@store.write('fodder', 'bother')
|
92
|
-
@store.write('yoo', 'yar')
|
93
|
-
# Initial state
|
94
|
-
@store.read('foo').should == 'bar'
|
95
|
-
@store.read('fodder').should == 'bother'
|
96
|
-
@store.read('yoo').should == 'yar'
|
97
|
-
# The work
|
98
|
-
@store.delete_matched /oo/
|
99
|
-
# Post state
|
100
|
-
@store.read('foo').should be_nil
|
101
|
-
@store.read('fodder').should == 'bother'
|
102
|
-
@store.read('yoo').should be_nil
|
103
|
-
end
|
104
|
-
|
105
|
-
it "can expire a value with the :expires_in option" do
|
106
|
-
@store.write('ray', 'dar', :expires_in => 2.seconds)
|
107
|
-
@store.read('ray').should == 'dar'
|
108
|
-
sleep(3)
|
109
|
-
@store.read('ray').should be_nil
|
110
|
-
end
|
111
|
-
|
112
|
-
after(:each) do
|
113
|
-
@store.collection.remove # Clear our records
|
114
|
-
end
|
115
|
-
after(:all) do
|
116
|
-
c = Mongo::Connection.new
|
117
|
-
c.drop_database('mongo_store_test')
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|