ngmoco-cache-money 0.2.10 → 0.2.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -36,10 +36,7 @@ module Cash
36
36
  end
37
37
 
38
38
  def shallow_clone
39
- clone = self.class.new
40
- clone.instance_variable_set("@attributes", instance_variable_get(:@attributes))
41
- clone.instance_variable_set("@new_record", new_record?)
42
- clone
39
+ self.class.send(:instantiate, instance_variable_get(:@attributes))
43
40
  end
44
41
 
45
42
  private
@@ -53,19 +50,19 @@ module Cash
53
50
 
54
51
  module ClassMethods
55
52
  def add_to_caches(object)
56
- indices.each { |index| index.add(object) }
53
+ indices.each { |index| index.add(object) } if cache_config
57
54
  end
58
55
 
59
56
  def update_caches(object)
60
- indices.each { |index| index.update(object) }
57
+ indices.each { |index| index.update(object) } if cache_config
61
58
  end
62
59
 
63
60
  def remove_from_caches(object)
64
- indices.each { |index| index.remove(object) }
61
+ indices.each { |index| index.remove(object) } if cache_config
65
62
  end
66
63
 
67
64
  def expire_caches(object)
68
- indices.each { |index| index.delete(object) }
65
+ indices.each { |index| index.delete(object) } if cache_config
69
66
  end
70
67
  end
71
68
  end
@@ -0,0 +1,49 @@
1
+ # begin
2
+ require 'memcached'
3
+
4
+ class MemCachedSessionStore < ActionController::Session::AbstractStore
5
+ def initialize(app, options = {})
6
+ # Support old :expires option
7
+ options[:expire_after] ||= options[:expires]
8
+
9
+ super
10
+
11
+ @default_options = {
12
+ :namespace => 'rack:session',
13
+ :servers => 'localhost:11211'
14
+ }.merge(@default_options)
15
+
16
+ @default_options[:prefix_key] ||= @default_options[:namespace]
17
+
18
+ @pool = options[:cache] || Memcached.new(@default_options[:servers], @default_options)
19
+ # unless @pool.servers.any? { |s| s.alive? }
20
+ # raise "#{self} unable to find server during initialization."
21
+ # end
22
+ @mutex = Mutex.new
23
+
24
+ super
25
+ end
26
+
27
+ private
28
+ def get_session(env, sid)
29
+ sid ||= generate_sid
30
+ begin
31
+ session = @pool.get(sid) || {}
32
+ rescue Memcached::NotFound, MemCache::MemCacheError, Errno::ECONNREFUSED
33
+ session = {}
34
+ end
35
+ [sid, session]
36
+ end
37
+
38
+ def set_session(env, sid, session_data)
39
+ options = env['rack.session.options']
40
+ expiry = options[:expire_after] || 0
41
+ @pool.set(sid, session_data, expiry)
42
+ return true
43
+ rescue Memcached::NotStored, MemCache::MemCacheError, Errno::ECONNREFUSED
44
+ return false
45
+ end
46
+ end
47
+ # rescue LoadError
48
+ # # Memcached wasn't available so neither can the store be
49
+ # end
@@ -0,0 +1,135 @@
1
+ require 'memcached'
2
+
3
+ # A cache store implementation which stores data in Memcached:
4
+ # http://www.danga.com/memcached/
5
+ #
6
+ # This is currently the most popular cache store for production websites.
7
+ #
8
+ # Special features:
9
+ # - Clustering and load balancing. One can specify multiple memcached servers,
10
+ # and MemCacheStore will load balance between all available servers. If a
11
+ # server goes down, then MemCacheStore will ignore it until it goes back
12
+ # online.
13
+ # - Time-based expiry support. See #write and the +:expires_in+ option.
14
+ # - Per-request in memory cache for all communication with the MemCache server(s).
15
+ class MemCachedSupportStore < ActiveSupport::Cache::Store
16
+
17
+ attr_reader :addresses
18
+
19
+ # Creates a new MemCacheStore object, with the given memcached server
20
+ # addresses. Each address is either a host name, or a host-with-port string
21
+ # in the form of "host_name:port". For example:
22
+ #
23
+ # ActiveSupport::Cache::MemCacheStore.new("localhost", "server-downstairs.localnetwork:8229")
24
+ #
25
+ # If no addresses are specified, then MemCacheStore will connect to
26
+ # localhost port 11211 (the default memcached port).
27
+ def initialize(*addresses)
28
+ addresses = addresses.flatten
29
+ options = addresses.extract_options!
30
+ options[:prefix_key] ||= options[:namespace]
31
+ addresses = ["localhost"] if addresses.empty?
32
+ @addresses = addresses
33
+ @data = Memcached.new(addresses, options)
34
+
35
+ extend ActiveSupport::Cache::Strategy::LocalCache
36
+ end
37
+
38
+ def read(key, options = nil) # :nodoc:
39
+ super
40
+ @data.get(key, marshal?(options))
41
+ rescue Memcached::NotFound
42
+ nil
43
+ rescue Memcached::Error => e
44
+ logger.error("MemcachedError (#{e}): #{e.message}")
45
+ nil
46
+ end
47
+
48
+ # Writes a value to the cache.
49
+ #
50
+ # Possible options:
51
+ # - +:unless_exist+ - set to true if you don't want to update the cache
52
+ # if the key is already set.
53
+ # - +:expires_in+ - the number of seconds that this value may stay in
54
+ # the cache. See ActiveSupport::Cache::Store#write for an example.
55
+ def write(key, value, options = nil)
56
+ super
57
+ method = options && options[:unless_exist] ? :add : :set
58
+ # memcache-client will break the connection if you send it an integer
59
+ # in raw mode, so we convert it to a string to be sure it continues working.
60
+ @data.send(method, key, value, expires_in(options), marshal?(options))
61
+ true
62
+ rescue Memcached::NotStored
63
+ false
64
+ rescue Memcached::NotFound
65
+ false
66
+ rescue Memcached::Error => e
67
+ logger.error("MemcachedError (#{e}): #{e.message}")
68
+ false
69
+ end
70
+
71
+ def delete(key, options = nil) # :nodoc:
72
+ super
73
+ @data.delete(key)
74
+ true
75
+ rescue Memcached::NotFound
76
+ false
77
+ rescue Memcached::Error => e
78
+ logger.error("MemcachedError (#{e}): #{e.message}")
79
+ false
80
+ end
81
+
82
+ def exist?(key, options = nil) # :nodoc:
83
+ # Doesn't call super, cause exist? in memcache is in fact a read
84
+ # But who cares? Reading is very fast anyway
85
+ # Local cache is checked first, if it doesn't know then memcache itself is read from
86
+ !read(key, options).nil?
87
+ end
88
+
89
+ def increment(key, amount = 1) # :nodoc:
90
+ log("incrementing", key, amount)
91
+
92
+ @data.incr(key, amount)
93
+ response
94
+ rescue Memcached::NotFound
95
+ nil
96
+ rescue Memcached::Error
97
+ nil
98
+ end
99
+
100
+ def decrement(key, amount = 1) # :nodoc:
101
+ log("decrement", key, amount)
102
+ @data.decr(key, amount)
103
+ response
104
+ rescue Memcached::NotFound
105
+ nil
106
+ rescue Memcached::Error
107
+ nil
108
+ end
109
+
110
+ def delete_matched(matcher, options = nil) # :nodoc:
111
+ # don't do any local caching at present, just pass
112
+ # through and let the error happen
113
+ super
114
+ raise "Not supported by Memcache"
115
+ end
116
+
117
+ def clear
118
+ @data.flush
119
+ rescue Memcached::NotFound
120
+ end
121
+
122
+ def stats
123
+ @data.stats
124
+ rescue Memcached::NotFound
125
+ end
126
+
127
+ private
128
+ def expires_in(options)
129
+ (options && options[:expires_in]) || 0
130
+ end
131
+
132
+ def marshal?(options)
133
+ !(options && options[:raw])
134
+ end
135
+ end
@@ -26,7 +26,7 @@ end
26
26
  class MemcachedWrapper < ::Memcached
27
27
  DEFAULTS = { :servers => '127.0.0.1:11211' }
28
28
 
29
- attr_reader :logger
29
+ attr_reader :logger, :default_ttl
30
30
 
31
31
  # See Memcached#new for details.
32
32
  def initialize(*args)
@@ -253,7 +253,8 @@ private
253
253
  end
254
254
 
255
255
  def log_error(err)
256
- logger.error("#{err}: \n\t#{err.backtrace.join("\n\t")}") if logger
256
+ #logger.error("#{err}: \n\t#{err.backtrace.join("\n\t")}") if logger
257
+ logger.error("Memcached ERROR, #{err.class}: #{err}") if logger
257
258
  end
258
259
 
259
260
  end
@@ -2,7 +2,7 @@ yml = YAML.load(IO.read(File.join(RAILS_ROOT, "config", "memcached.yml")))
2
2
  memcache_config = yml[RAILS_ENV]
3
3
  memcache_config.symbolize_keys! if memcache_config.respond_to?(:symbolize_keys!)
4
4
 
5
- if defined?(DISABLE_CACHE_MONEY) || memcache_config.nil? || memcache_config[:cache_money] != true
5
+ if defined?(DISABLE_CACHE_MONEY) || ENV['DISABLE_CACHE_MONEY'] == 'true' || memcache_config.nil? || memcache_config[:cache_money] != true
6
6
  Rails.logger.info 'cache-money disabled'
7
7
  class ActiveRecord::Base
8
8
  def self.index(*args)
@@ -13,7 +13,12 @@ else
13
13
  require 'cache_money'
14
14
 
15
15
  memcache_config[:logger] = Rails.logger
16
- $memcache = MemcachedWrapper.new(memcache_config[:servers].gsub(' ', '').split(','), memcache_config)
16
+ memcache_servers =
17
+ case memcache_config[:servers].class.to_s
18
+ when "String"; memcache_config[:servers].gsub(' ', '').split(',')
19
+ when "Array"; memcache_config[:servers]
20
+ end
21
+ $memcache = MemcachedWrapper.new(memcache_servers, memcache_config)
17
22
 
18
23
  #ActionController::Base.cache_store = :cache_money_mem_cache_store
19
24
  ActionController::Base.session_options[:cache] = $memcache if memcache_config[:sessions]
@@ -25,15 +30,11 @@ else
25
30
  $lock = Cash::Lock.new($memcache)
26
31
  $cache = Cash::Transactional.new($local, $lock)
27
32
 
28
- class ActiveRecord::Base
29
- is_cached(:repository => $cache)
30
-
31
- def <=>(other)
32
- if self.id == other.id then
33
- 0
34
- else
35
- self.id < other.id ? -1 : 1
36
- end
33
+ # allow setting up caching on a per-model basis
34
+ unless memcache_config[:automatic_caching].to_s == 'false'
35
+ Rails.logger.info "cache-money: global model caching enabled"
36
+ class ActiveRecord::Base
37
+ is_cached(:repository => $cache)
37
38
  end
38
39
  end
39
40
  end
@@ -1,4 +1,5 @@
1
- require File.join(File.dirname(__FILE__), '..', 'spec_helper')
1
+ require "spec_helper"
2
+ require 'ruby-debug'
2
3
 
3
4
  module Cash
4
5
  describe Accessor do
@@ -80,10 +81,38 @@ module Cash
80
81
  end
81
82
  end
82
83
 
84
+ describe '#add' do
85
+ describe 'when the value already exists' do
86
+ describe 'when a block is given' do
87
+ it 'yields to the block' do
88
+ Story.set("count", 1)
89
+ Story.add("count", 1) { "yield me" }.should == "yield me"
90
+ end
91
+ end
92
+
93
+ describe 'when no block is given' do
94
+ it 'does not error' do
95
+ Story.set("count", 1)
96
+ lambda { Story.add("count", 1) }.should_not raise_error
97
+ end
98
+ end
99
+ end
100
+
101
+ describe 'when the value does not already exist' do
102
+ it 'adds the key to the cache' do
103
+ Story.add("count", 1)
104
+ Story.get("count").should == 1
105
+ end
106
+ end
107
+ end
108
+
109
+ describe '#set' do
110
+ end
111
+
83
112
  describe '#incr' do
84
113
  describe 'when there is a cache hit' do
85
114
  before do
86
- Story.set("count", 0)
115
+ Story.set("count", 0, :raw => true)
87
116
  end
88
117
 
89
118
  it 'increments the value of the cache' do
@@ -108,31 +137,6 @@ module Cash
108
137
  end
109
138
  end
110
139
 
111
- describe '#add' do
112
- describe 'when the value already exists' do
113
- describe 'when a block is given' do
114
- it 'yields to the block' do
115
- Story.set("count", 1)
116
- Story.add("count", 1) { "yield me" }.should == "yield me"
117
- end
118
- end
119
-
120
- describe 'when no block is given' do
121
- it 'does not error' do
122
- Story.set("count", 1)
123
- lambda { Story.add("count", 1) }.should_not raise_error
124
- end
125
- end
126
- end
127
-
128
- describe 'when the value does not already exist' do
129
- it 'adds the key to the cache' do
130
- Story.add("count", 1)
131
- Story.get("count").should == 1
132
- end
133
- end
134
- end
135
-
136
140
  describe '#decr' do
137
141
  describe 'when there is a cache hit' do
138
142
  before do
@@ -161,6 +165,14 @@ module Cash
161
165
  end
162
166
  end
163
167
 
168
+ describe '#expire' do
169
+ it 'deletes the key' do
170
+ Story.set("bobo", 1)
171
+ Story.expire("bobo")
172
+ Story.get("bobo").should == nil
173
+ end
174
+ end
175
+
164
176
  describe '#cache_key' do
165
177
  it 'uses the version number' do
166
178
  Story.version 1
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), '..', 'spec_helper')
1
+ require "spec_helper"
2
2
 
3
3
  module Cash
4
4
  describe Finders do
@@ -207,5 +207,18 @@ module Cash
207
207
  end
208
208
  end
209
209
  end
210
+
211
+ describe 'loading' do
212
+ it "should be able to create a record for an ar subclass that was loaded before cache money" do
213
+ $debug = true
214
+ session = ActiveRecord::SessionStore::Session.new
215
+ session.session_id = "12345"
216
+ session.data = "foobarbaz"
217
+
218
+ lambda {
219
+ session.save!
220
+ }.should_not raise_error
221
+ end
222
+ end
210
223
  end
211
224
  end
@@ -0,0 +1,9 @@
1
+ require "spec_helper"
2
+
3
+ module Cash
4
+ describe Buffered do
5
+ it "should have method missing as a private method" do
6
+ Buffered.private_instance_methods.should include("method_missing")
7
+ end
8
+ end
9
+ end
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), '..', 'spec_helper')
1
+ require "spec_helper"
2
2
 
3
3
  module Cash
4
4
  describe Finders do
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(__FILE__), '..', 'spec_helper')
1
+ require "spec_helper"
2
2
 
3
3
  module Cash
4
4
  describe Finders do
@@ -339,10 +339,20 @@ module Cash
339
339
  end
340
340
 
341
341
  describe '#find_by_attr' do
342
+ before(:each) do
343
+ Story.find_by_title(@story.title) # populates cache for title with [@story.id]
344
+ end
345
+
342
346
  it 'populates the cache' do
343
- Story.find_by_title(@story.title)
344
347
  Story.fetch("title/#{@story.title}").should == [@story.id]
345
348
  end
349
+
350
+ it 'populates the cache when finding by non-primary-key attribute' do
351
+ Story.find_by_title(@story.title) # populates cache for id with record
352
+
353
+ mock(Story.connection).execute.never # should hit cache only
354
+ Story.find_by_title(@story.title).id.should == @story.id
355
+ end
346
356
  end
347
357
 
348
358
  describe '#find(:all, :conditions => ...)' do
@@ -352,6 +362,32 @@ module Cash
352
362
  end
353
363
  end
354
364
 
365
+ describe '#find(:all, :conditions => ..., :order => ...)' do
366
+ before(:each) do
367
+ @short1 = Short.create(:title => 'title',
368
+ :subtitle => 'subtitle')
369
+ @short2 = Short.create(:title => 'another title',
370
+ :subtitle => 'subtitle')
371
+ $memcache.flush_all
372
+ Short.find(:all, :conditions => { :subtitle => @short1.subtitle },
373
+ :order => 'title')
374
+ end
375
+
376
+ it 'populates the cache' do
377
+ Short.fetch("subtitle/subtitle").should_not be_blank
378
+ end
379
+
380
+ it 'returns objects in the correct order' do
381
+ Short.fetch("subtitle/subtitle").should ==
382
+ [@short2.id, @short1.id]
383
+ end
384
+
385
+ it 'finds objects in the correct order' do
386
+ Short.find(:all, :conditions => { :subtitle => @short1.subtitle },
387
+ :order => 'title').map(&:id).should == [@short2.id, @short1.id]
388
+ end
389
+ end
390
+
355
391
  describe '#find(1)' do
356
392
  it 'populates the cache' do
357
393
  Story.find(@story.id)