ngmoco-cache-money 0.2.10 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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)