diff --git a/.rvmrc b/.rvmrc new file mode 100644 index 0000000..e41e3b0 --- /dev/null +++ b/.rvmrc @@ -0,0 +1,5 @@ +if [[ -n "$rvm_environments_path" && -s "$rvm_environments_path/ruby-1.9.2-p136@mongo_store" ]] ; then + \. "$rvm_environments_path/ruby-1.9.2-p136@mongo_store" +else + rvm --create use "ruby-1.9.2-p136@mongo_store" +fi \ No newline at end of file diff --git a/History.markdown b/History.markdown index 918b3c2..3d3cb14 100644 --- a/History.markdown +++ b/History.markdown @@ -1,3 +1,7 @@ +0.3.1 / 2011-03-17 +================== + * Added in an optimised 'read_multi' function for Rails 3 - performs a single db call rather than one for each key + 0.3.0 / 2010-08-30 ================== * We have a History file now diff --git a/Rakefile b/Rakefile index fd43566..145bc1b 100644 --- a/Rakefile +++ b/Rakefile @@ -22,22 +22,6 @@ rescue LoadError puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler" end -require 'spec/rake/spectask' -Spec::Rake::SpecTask.new(:spec) do |spec| - spec.libs << 'lib' << 'spec' - spec.spec_files = FileList['spec/**/*_spec.rb'] -end - -Spec::Rake::SpecTask.new(:rcov) do |spec| - spec.libs << 'lib' << 'spec' - spec.pattern = 'spec/**/*_spec.rb' - spec.rcov = true -end - -task :spec => :check_dependencies - -task :default => :spec - require 'rake/rdoctask' Rake::RDocTask.new do |rdoc| version = File.exist?('VERSION') ? File.read('VERSION') : "" diff --git a/lib/active_support/cache/mongo_store.rb b/lib/active_support/cache/mongo_store.rb index 82eb7f6..f74a75a 100644 --- a/lib/active_support/cache/mongo_store.rb +++ b/lib/active_support/cache/mongo_store.rb @@ -24,7 +24,7 @@ def read(key, local_options=nil) doc['value'] end end - + # Takes the specified value out of the collection. def delete(key, local_options=nil) super @@ -32,44 +32,43 @@ def delete(key, local_options=nil) collection.remove({'_id' => namespaced_key(key,opts)}) end - # Takes the value matching the pattern out of the collection. def delete_matched(key, local_options=nil) super opts = local_options ? options.merge(local_options) : options collection.remove({'_id' => key_matcher(key,opts)}) end - - - protected - - # Lifted from Rails 3 ActiveSupport::Cache::Store - def namespaced_key(key, options) - namespace = options[:namespace] if options - prefix = namespace.is_a?(Proc) ? namespace.call : namespace - key = "#{prefix}:#{key}" if prefix - key - end - # Lifted from Rails 3 ActiveSupport::Cache::Store - def key_matcher(pattern, options) - prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace] - if prefix - source = pattern.source - if source.start_with?('^') - source = source[1, source.length] + protected + + # Lifted from Rails 3 ActiveSupport::Cache::Store + def namespaced_key(key, options) + namespace = options[:namespace] if options + prefix = namespace.is_a?(Proc) ? namespace.call : namespace + key = "#{prefix}:#{key}" if prefix + key + end + + # Lifted from Rails 3 ActiveSupport::Cache::Store + def key_matcher(pattern, options) + prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace] + if prefix + source = pattern.source + if source.start_with?('^') + source = source[1, source.length] + else + source = ".*#{source[0, source.length]}" + end + Regexp.new("^#{Regexp.escape(prefix)}:#{source}", pattern.options) else - source = ".*#{source[0, source.length]}" + pattern end - Regexp.new("^#{Regexp.escape(prefix)}:#{source}", pattern.options) - else - pattern end - end end module Rails3 + def write_entry(key, entry, options) expires = Time.now + options[:expires_in] value = entry.value @@ -80,13 +79,31 @@ def write_entry(key, entry, options) value = value.to_s and retry unless value.is_a? String end end + def read_entry(key, options=nil) doc = collection.find_one('_id' => key, 'expires' => {'$gt' => Time.now}) ActiveSupport::Cache::Entry.new(doc['value']) if doc end + + def read_multi(*keys) + docs = {} + keys.each do |key| + docs[key] = nil + end + + collection.find({ '_id' => {'$in' => keys}, 'expires' => {'$gt' => Time.now} }, :timeout => false ) do |cursor| + cursor.each do |doc| + docs[ doc['_id'] ] = doc['value'] + end + end + + return docs + end + def delete_entry(key, options=nil) collection.remove({'_id' => key}) end + def delete_matched(pattern, options=nil) options = merged_options(options) instrument(:delete_matched, pattern.inspect) do @@ -94,6 +111,7 @@ def delete_matched(pattern, options=nil) delete_entry(matcher, options) end end + end module Store @@ -154,7 +172,7 @@ def initialize(collection = nil, options = nil) def collection @collection ||= make_collection end - + # Removes old cached values that have expired. Set this up to run occasionally in delayed_job, etc., if you # start worrying about space. (In practice, because we favor updating over inserting, space is only wasted # if the key itself never gets cached again. It also means you can _reduce_ efficiency by running this @@ -169,25 +187,26 @@ def clear end private - def mongomapper? - Kernel.const_defined?(:MongoMapper) && MongoMapper.respond_to?(:database) && MongoMapper.database - end - - def make_collection - db = case options[:db] - when Mongo::DB then options[:db] - when String then Mongo::DB.new(options[:db], Mongo::Connection.new) - else - if mongomapper? - MongoMapper.database + + def mongomapper? + Kernel.const_defined?(:MongoMapper) && MongoMapper.respond_to?(:database) && MongoMapper.database + end + + def make_collection + db = case options[:db] + when Mongo::DB then options[:db] + when String then Mongo::DB.new(options[:db], Mongo::Connection.new) else - Mongo::DB.new(options[:db_name], Mongo::Connection.new) + if mongomapper? + MongoMapper.database + else + Mongo::DB.new(options[:db_name], Mongo::Connection.new) + end end + coll = db.create_collection(options[:collection_name]) + coll.create_index([['_id',Mongo::ASCENDING], ['expires',Mongo::DESCENDING]]) if options[:create_index] + coll end - coll = db.create_collection(options[:collection_name]) - coll.create_index([['_id',Mongo::ASCENDING], ['expires',Mongo::DESCENDING]]) if options[:create_index] - coll - end end end diff --git a/mongo_store.gemspec b/mongo_store.gemspec index 7e2c306..c3cc38a 100644 --- a/mongo_store.gemspec +++ b/mongo_store.gemspec @@ -5,7 +5,7 @@ Gem::Specification.new do |s| s.name = %q{mongo_store} - s.version = "0.3.0" + s.version = "0.3.1" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Stephen Eley"] diff --git a/spec/active_support/cache/mongo_store_spec.rb b/spec/active_support/cache/mongo_store_spec.rb index 27a02d4..c2b4ede 100644 --- a/spec/active_support/cache/mongo_store_spec.rb +++ b/spec/active_support/cache/mongo_store_spec.rb @@ -10,11 +10,11 @@ module Cache describe "initializing" do it "can take a Mongo::Collection object" do db = Mongo::DB.new('mongo_store_test', Mongo::Connection.new) - coll = Mongo::Collection.new(db, 'foostore') + coll = Mongo::Collection.new('foostore', db) store = ActiveSupport::Cache.lookup_store(:mongo_store, coll) store.collection.should == coll end - + it "can take a collection name" do store = ActiveSupport::Cache.lookup_store(:mongo_store, 'foo') store.collection.name.should == 'foo' @@ -50,7 +50,6 @@ module Cache MongoMapper.expects(:database).at_least_once.returns(lazy) store.collection.db.should == lazy end - it "defaults the database name to 'rails_cache'" do store = ActiveSupport::Cache.lookup_store(:mongo_store) @@ -88,8 +87,7 @@ module Cache store = ActiveSupport::Cache.lookup_store(:mongo_store, 'foo', :expires_in => 1.day) store.expires_in.should == 1.day end - - + after(:all) do c = Mongo::Connection.new %w(rails_cache mongo_store_test_name mongo_store_test_deebee mongo_store_test_mappy mongo_store_test_lazy).each do |db| @@ -97,29 +95,36 @@ module Cache end end end - + describe "caching" do before(:all) do @store = ActiveSupport::Cache.lookup_store(:mongo_store, 'mongo_store_test', :db => 'mongo_store_test') end - + it "can write values" do @store.write('fnord', 'I am vaguely disturbed.') @store.collection.find_one('_id' => 'fnord')['value'].should == "I am vaguely disturbed." end - + it "can read values" do @store.collection.insert({'_id' => 'yoo', :value => 'yar', :expires => (Time.now + 10)}) @store.read('yoo').should == 'yar' end - + + it "can read multiple values" do + @store.collection.insert({'_id' => 'multi1', :value => 'weee', :expires => (Time.now + 10)}) + @store.collection.insert({'_id' => 'multi2', :value => 'wooo', :expires => (Time.now + 10)}) + @store.read_multi('multi1','multi2').should == { 'multi1' => 'weee', 'multi2' => 'wooo' } + @store.read_multi('multi1','multi2','multi3').should == { 'multi1' => 'weee', 'multi2' => 'wooo', 'multi3' => nil } + end + it "can delete keys" do @store.write('foo', 'bar') @store.read('foo').should == 'bar' @store.delete('foo') @store.read('foo').should be_nil end - + it "can delete keys matching a regular expression" do @store.write('foo', 'bar') @store.write('fodder', 'bother') @@ -135,7 +140,7 @@ module Cache @store.read('fodder').should == 'bother' @store.read('yoo').should be_nil end - + it "can expire a value with the :expires_in option" do @store.write('ray', 'dar', :expires_in => 2.seconds) @store.read('ray').should == 'dar' @@ -164,7 +169,7 @@ module Cache @store.clean_expired @store.collection.count.should == 1 end - + it "can clear the whole cache" do @store.write('foo', 'bar') @store.write('yoo', 'yar', :expires_in => 2.days) @@ -172,6 +177,7 @@ module Cache @store.clear @store.collection.count.should == 0 end + describe "namespacing" do before(:each) do @store.options[:namespace] = 'ns1' @@ -227,9 +233,9 @@ module Cache @store.read('too', :namespace => 'ns2').should be_nil @store.read('foz', :namespace => 'ns2').should == 'bat' end - + end - + after(:each) do @store.collection.remove # Clear our records end