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 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{ActiveSupport::Cache implementation for MongoDB}
9
- gem.description = %Q{10gen keeps bragging that MongoDB, with its fast writes and memory-mapped architecture, is ideal for caching. This gem puts it to use in Rails.}
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
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
- class MongoStore < Store
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
@@ -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
@@ -1,5 +1,6 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__))
2
2
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'mongo'
3
4
  require 'mongo_store'
4
5
  require 'spec'
5
6
  require 'spec/autorun'
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 1
7
+ - 2
8
8
  - 0
9
- version: 0.1.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: 10gen keeps bragging that MongoDB, with its fast writes and memory-mapped architecture, is ideal for caching. This gem puts it to use in Rails.
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.rdoc
82
+ - README.markdown
83
83
  files:
84
84
  - .document
85
85
  - .gitignore
86
86
  - LICENSE
87
- - README.rdoc
87
+ - README.markdown
88
88
  - Rakefile
89
89
  - VERSION
90
+ - lib/active_support/cache/mongo_store.rb
90
91
  - lib/mongo_store.rb
91
- - spec/mongo_store_spec.rb
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: ActiveSupport::Cache implementation for MongoDB
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.
@@ -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