sreeix-cache-money 0.2.24.1
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.
- data/LICENSE +201 -0
- data/README +210 -0
- data/README.markdown +210 -0
- data/TODO +17 -0
- data/UNSUPPORTED_FEATURES +13 -0
- data/config/environment.rb +16 -0
- data/config/memcached.yml +6 -0
- data/db/schema.rb +18 -0
- data/init.rb +1 -0
- data/lib/cache_money.rb +91 -0
- data/lib/cash/accessor.rb +83 -0
- data/lib/cash/buffered.rb +129 -0
- data/lib/cash/config.rb +75 -0
- data/lib/cash/fake.rb +83 -0
- data/lib/cash/finders.rb +38 -0
- data/lib/cash/index.rb +214 -0
- data/lib/cash/local.rb +76 -0
- data/lib/cash/lock.rb +63 -0
- data/lib/cash/mock.rb +154 -0
- data/lib/cash/query/abstract.rb +210 -0
- data/lib/cash/query/calculation.rb +45 -0
- data/lib/cash/query/primary_key.rb +50 -0
- data/lib/cash/query/select.rb +16 -0
- data/lib/cash/request.rb +3 -0
- data/lib/cash/transactional.rb +43 -0
- data/lib/cash/util/array.rb +9 -0
- data/lib/cash/util/marshal.rb +19 -0
- data/lib/cash/write_through.rb +69 -0
- data/lib/mem_cached_session_store.rb +49 -0
- data/lib/mem_cached_support_store.rb +135 -0
- data/lib/memcached_wrapper.rb +263 -0
- data/rails/init.rb +38 -0
- data/spec/cash/accessor_spec.rb +159 -0
- data/spec/cash/active_record_spec.rb +224 -0
- data/spec/cash/buffered_spec.rb +9 -0
- data/spec/cash/calculations_spec.rb +78 -0
- data/spec/cash/finders_spec.rb +430 -0
- data/spec/cash/local_buffer_spec.rb +9 -0
- data/spec/cash/local_spec.rb +9 -0
- data/spec/cash/lock_spec.rb +108 -0
- data/spec/cash/marshal_spec.rb +60 -0
- data/spec/cash/order_spec.rb +138 -0
- data/spec/cash/shared.rb +62 -0
- data/spec/cash/transactional_spec.rb +578 -0
- data/spec/cash/window_spec.rb +195 -0
- data/spec/cash/write_through_spec.rb +245 -0
- data/spec/memcached_wrapper_test.rb +209 -0
- data/spec/spec_helper.rb +60 -0
- metadata +151 -0
@@ -0,0 +1,263 @@
|
|
1
|
+
# require 'memcache'
|
2
|
+
# require 'memcached'
|
3
|
+
|
4
|
+
#typically MemCache/Memcached can be used via the following in rails/init.rb:
|
5
|
+
#$memcache = MemCache.new(memcache_config[:servers].gsub(' ', '').split(','), memcache_config)
|
6
|
+
#$memcache = Memcached::Rails.new(memcache_config[:servers].gsub(' ', '').split(','), memcache_config)
|
7
|
+
|
8
|
+
#this wrapper lets both work.
|
9
|
+
|
10
|
+
########## they have Memcached installed (do need the wrapper)
|
11
|
+
if defined? Memcached
|
12
|
+
Rails.logger.info("cache-money: Memcached installed") if defined? Rails
|
13
|
+
|
14
|
+
class Memcached
|
15
|
+
alias :get_multi :get #:nodoc:
|
16
|
+
end
|
17
|
+
|
18
|
+
class MemcachedWrapper < ::Memcached
|
19
|
+
DEFAULTS = { :servers => '127.0.0.1:11211' }
|
20
|
+
|
21
|
+
attr_reader :logger, :default_ttl
|
22
|
+
|
23
|
+
# See Memcached#new for details.
|
24
|
+
def initialize(*args)
|
25
|
+
opts = DEFAULTS.merge(args.last.is_a?(Hash) ? args.pop : {})
|
26
|
+
|
27
|
+
if opts.respond_to?(:symbolize_keys!)
|
28
|
+
opts.symbolize_keys!
|
29
|
+
else
|
30
|
+
opts = symbolize_keys(opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
servers = Array(
|
34
|
+
args.any? ? args.unshift : opts.delete(:servers)
|
35
|
+
).flatten.compact
|
36
|
+
|
37
|
+
opts[:prefix_key] ||= "#{opts[:namespace]}:"
|
38
|
+
|
39
|
+
@logger = opts[:logger]
|
40
|
+
@debug = opts[:debug]
|
41
|
+
|
42
|
+
super(servers, opts)
|
43
|
+
end
|
44
|
+
|
45
|
+
def symbolize_keys(opts)
|
46
|
+
# Destructively convert all keys to symbols.
|
47
|
+
if opts.kind_of?(Hash) && !opts.kind_of?(HashWithIndifferentAccess)
|
48
|
+
opts.keys.each do |key|
|
49
|
+
unless key.is_a?(Symbol)
|
50
|
+
opts[key.to_sym] = opts[key]
|
51
|
+
opts.delete(key)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
opts
|
56
|
+
end
|
57
|
+
|
58
|
+
def namespace
|
59
|
+
options[:prefix_key]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Wraps Memcached::Rails#add to return a text string - for cache money
|
63
|
+
def add(key, value, ttl=@default_ttl, raw=false)
|
64
|
+
logger.debug("Memcached add: #{key.inspect}") if logger && @debug
|
65
|
+
super(key, value, ttl, !raw)
|
66
|
+
logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
|
67
|
+
stored
|
68
|
+
rescue Memcached::NotStored
|
69
|
+
logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
|
70
|
+
not_stored
|
71
|
+
rescue Memcached::Error
|
72
|
+
log_error($!) if logger
|
73
|
+
not_stored
|
74
|
+
end
|
75
|
+
|
76
|
+
def replace(key, value, ttl = @default_ttl, raw = false)
|
77
|
+
logger.debug("Memcached replace: #{key.inspect}") if logger && @debug
|
78
|
+
super(key, value, ttl, !raw)
|
79
|
+
logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
|
80
|
+
stored
|
81
|
+
rescue Memcached::NotStored
|
82
|
+
logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
|
83
|
+
not_stored
|
84
|
+
rescue Memcached::Error
|
85
|
+
log_error($!) if logger
|
86
|
+
not_stored
|
87
|
+
end
|
88
|
+
|
89
|
+
# Wraps Memcached#get so that it doesn't raise. This has the side-effect of preventing you from
|
90
|
+
# storing <tt>nil</tt> values.
|
91
|
+
def get(key, raw=false)
|
92
|
+
logger.debug("Memcached get: #{key.inspect}") if logger && @debug
|
93
|
+
value = super(key, !raw)
|
94
|
+
logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
|
95
|
+
value
|
96
|
+
rescue Memcached::NotFound
|
97
|
+
logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
|
98
|
+
nil
|
99
|
+
rescue TypeError
|
100
|
+
log_error($!) if logger
|
101
|
+
delete(key)
|
102
|
+
logger.debug("Memcached deleted: #{key.inspect}") if logger && @debug
|
103
|
+
nil
|
104
|
+
rescue Memcached::Error
|
105
|
+
log_error($!) if logger
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def fetch(key, expiry = 0, raw = false)
|
110
|
+
value = get(key, !raw)
|
111
|
+
|
112
|
+
if value.nil? && block_given?
|
113
|
+
value = yield
|
114
|
+
add(key, value, expiry, !raw)
|
115
|
+
end
|
116
|
+
|
117
|
+
value
|
118
|
+
end
|
119
|
+
|
120
|
+
# Wraps Memcached#cas so that it doesn't raise. Doesn't set anything if no value is present.
|
121
|
+
def cas(key, ttl=@default_ttl, raw=false, &block)
|
122
|
+
logger.debug("Memcached cas: #{key.inspect}") if logger && @debug
|
123
|
+
super(key, ttl, !raw, &block)
|
124
|
+
logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
|
125
|
+
stored
|
126
|
+
rescue Memcached::NotFound
|
127
|
+
logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
|
128
|
+
rescue TypeError
|
129
|
+
log_error($!) if logger
|
130
|
+
delete(key)
|
131
|
+
logger.debug("Memcached deleted: #{key.inspect}") if logger && @debug
|
132
|
+
rescue Memcached::Error
|
133
|
+
if $!.is_a?(Memcached::ClientError)
|
134
|
+
raise $!
|
135
|
+
end
|
136
|
+
log_error($!) if logger
|
137
|
+
end
|
138
|
+
|
139
|
+
def get_multi(*keys)
|
140
|
+
keys.flatten!
|
141
|
+
logger.debug("Memcached get_multi: #{keys.inspect}") if logger && @debug
|
142
|
+
values = super(keys, true)
|
143
|
+
logger.debug("Memcached hit: #{keys.inspect}") if logger && @debug
|
144
|
+
values
|
145
|
+
rescue Memcached::NotFound
|
146
|
+
logger.debug("Memcached miss: #{keys.inspect}") if logger && @debug
|
147
|
+
{}
|
148
|
+
rescue TypeError
|
149
|
+
log_error($!) if logger
|
150
|
+
keys.each { |key| delete(key) }
|
151
|
+
logger.debug("Memcached deleted: #{keys.inspect}") if logger && @debug
|
152
|
+
{}
|
153
|
+
rescue Memcached::Error
|
154
|
+
log_error($!) if logger
|
155
|
+
{}
|
156
|
+
end
|
157
|
+
|
158
|
+
def set(key, value, ttl=@default_ttl, raw=false)
|
159
|
+
logger.debug("Memcached set: #{key.inspect}") if logger && @debug
|
160
|
+
super(key, value, ttl, !raw)
|
161
|
+
logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
|
162
|
+
stored
|
163
|
+
rescue Memcached::Error
|
164
|
+
log_error($!) if logger
|
165
|
+
not_stored
|
166
|
+
end
|
167
|
+
|
168
|
+
def append(key, value)
|
169
|
+
logger.debug("Memcached append: #{key.inspect}") if logger && @debug
|
170
|
+
super(key, value)
|
171
|
+
logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
|
172
|
+
stored
|
173
|
+
rescue Memcached::NotStored
|
174
|
+
logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
|
175
|
+
not_stored
|
176
|
+
rescue Memcached::Error
|
177
|
+
log_error($!) if logger
|
178
|
+
end
|
179
|
+
|
180
|
+
def prepend(key, value)
|
181
|
+
logger.debug("Memcached prepend: #{key.inspect}") if logger && @debug
|
182
|
+
super(key, value)
|
183
|
+
logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
|
184
|
+
stored
|
185
|
+
rescue Memcached::NotStored
|
186
|
+
logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
|
187
|
+
not_stored
|
188
|
+
rescue Memcached::Error
|
189
|
+
log_error($!) if logger
|
190
|
+
end
|
191
|
+
|
192
|
+
def delete(key)
|
193
|
+
logger.debug("Memcached delete: #{key.inspect}") if logger && @debug
|
194
|
+
super(key)
|
195
|
+
logger.debug("Memcached hit: #{key.inspect}") if logger && @debug
|
196
|
+
deleted
|
197
|
+
rescue Memcached::NotFound
|
198
|
+
logger.debug("Memcached miss: #{key.inspect}") if logger && @debug
|
199
|
+
not_found
|
200
|
+
rescue Memcached::Error
|
201
|
+
log_error($!) if logger
|
202
|
+
end
|
203
|
+
|
204
|
+
def incr(*args)
|
205
|
+
super
|
206
|
+
rescue Memcached::NotFound
|
207
|
+
rescue Memcached::Error
|
208
|
+
log_error($!) if logger
|
209
|
+
end
|
210
|
+
|
211
|
+
def decr(*args)
|
212
|
+
super
|
213
|
+
rescue Memcached::NotFound
|
214
|
+
rescue Memcached::Error
|
215
|
+
log_error($!) if logger
|
216
|
+
end
|
217
|
+
|
218
|
+
def get_server_for_key(key, options = {})
|
219
|
+
server_by_key(key)
|
220
|
+
end
|
221
|
+
|
222
|
+
alias :reset :quit
|
223
|
+
alias :close :quit #nodoc
|
224
|
+
alias :flush_all :flush
|
225
|
+
alias :compare_and_swap :cas
|
226
|
+
alias :"[]" :get
|
227
|
+
alias :"[]=" :set
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
def stored
|
232
|
+
"STORED\r\n"
|
233
|
+
end
|
234
|
+
|
235
|
+
def deleted
|
236
|
+
"DELETED\r\n"
|
237
|
+
end
|
238
|
+
|
239
|
+
def not_stored
|
240
|
+
"NOT_STORED\r\n"
|
241
|
+
end
|
242
|
+
|
243
|
+
def not_found
|
244
|
+
"NOT_FOUND\r\n"
|
245
|
+
end
|
246
|
+
|
247
|
+
def log_error(err)
|
248
|
+
#logger.error("#{err}: \n\t#{err.backtrace.join("\n\t")}") if logger
|
249
|
+
logger.error("Memcached ERROR, #{err.class}: #{err}") if logger
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
####### they have MemCache installed (don't need the wrapper)
|
254
|
+
elsif defined? MemCache
|
255
|
+
|
256
|
+
Rails.logger.info("cache-money: MemCache installed") if defined? Rails
|
257
|
+
#TODO add logging?
|
258
|
+
class MemcachedWrapper < ::MemCache
|
259
|
+
end
|
260
|
+
|
261
|
+
else
|
262
|
+
Rails.logger.warn 'unable to determine memcache implementation' if defined? Rails
|
263
|
+
end #include the wraper
|
data/rails/init.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
yml = YAML.load(IO.read(File.join(RAILS_ROOT, "config", "memcached.yml")))
|
2
|
+
memcache_config = yml[RAILS_ENV]
|
3
|
+
memcache_config.symbolize_keys! if memcache_config.respond_to?(:symbolize_keys!)
|
4
|
+
|
5
|
+
memcache_config[:logger] = Rails.logger
|
6
|
+
memcache_servers =
|
7
|
+
case memcache_config[:servers].class.to_s
|
8
|
+
when "String"; memcache_config[:servers].gsub(' ', '').split(',')
|
9
|
+
when "Array"; memcache_config[:servers]
|
10
|
+
end
|
11
|
+
|
12
|
+
Rails.logger.info '$memcache enabled.'
|
13
|
+
|
14
|
+
$memcache = MemcachedWrapper.new(memcache_servers, memcache_config)
|
15
|
+
|
16
|
+
if defined?(DISABLE_CACHE_MONEY) || ENV['DISABLE_CACHE_MONEY'] == 'true' || memcache_config.nil? || memcache_config[:cache_money] != true
|
17
|
+
Rails.logger.info 'cache-money disabled'
|
18
|
+
class ActiveRecord::Base
|
19
|
+
def self.index(*args)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
Rails.logger.info 'cache-money enabled'
|
24
|
+
require 'cache_money'
|
25
|
+
|
26
|
+
ActionController::Base.session_options[:cache] = $memcache if memcache_config[:sessions]
|
27
|
+
$local = Cash::Local.new($memcache)
|
28
|
+
$lock = Cash::Lock.new($memcache)
|
29
|
+
$cache = Cash::Transactional.new($local, $lock)
|
30
|
+
|
31
|
+
# allow setting up caching on a per-model basis
|
32
|
+
unless memcache_config[:automatic_caching].to_s == 'false'
|
33
|
+
Rails.logger.info "cache-money: global model caching enabled"
|
34
|
+
class ActiveRecord::Base
|
35
|
+
is_cached(:repository => $cache)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require 'ruby-debug'
|
3
|
+
|
4
|
+
module Cash
|
5
|
+
describe Accessor do
|
6
|
+
describe '#fetch' do
|
7
|
+
describe '#fetch("...")' do
|
8
|
+
describe 'when there is a cache miss' do
|
9
|
+
it 'returns nil' do
|
10
|
+
Story.fetch("yabba").should be_nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'when there is a cache hit' do
|
15
|
+
it 'returns the value of the cache' do
|
16
|
+
Story.set("yabba", "dabba")
|
17
|
+
Story.fetch("yabba").should == "dabba"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#get' do
|
24
|
+
describe '#get("...")' do
|
25
|
+
describe 'when there is a cache miss' do
|
26
|
+
it 'returns the value of the block' do
|
27
|
+
Story.get("yabba") { "dabba" }.should == "dabba"
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'adds to the cache' do
|
31
|
+
Story.get("yabba") { "dabba" }
|
32
|
+
Story.get("yabba").should == "dabba"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'when there is a cache hit' do
|
37
|
+
before do
|
38
|
+
Story.set("yabba", "dabba")
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns the value of the cache' do
|
42
|
+
Story.get("yabba") { "doo" }.should == "dabba"
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'does nothing to the cache' do
|
46
|
+
Story.get("yabba") { "doo" }
|
47
|
+
Story.get("yabba").should == "dabba"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#get([...])' do
|
53
|
+
it_should_behave_like "#fetch([...])"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#add' do
|
58
|
+
describe 'when the value already exists' do
|
59
|
+
describe 'when a block is given' do
|
60
|
+
it 'yields to the block' do
|
61
|
+
Story.set("count", 1)
|
62
|
+
Story.add("count", 1) { "yield me" }.should == "yield me"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'when no block is given' do
|
67
|
+
it 'does not error' do
|
68
|
+
Story.set("count", 1)
|
69
|
+
lambda { Story.add("count", 1) }.should_not raise_error
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'when the value does not already exist' do
|
75
|
+
it 'adds the key to the cache' do
|
76
|
+
Story.add("count", 1)
|
77
|
+
Story.get("count").should == 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#set' do
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '#incr' do
|
86
|
+
describe 'when there is a cache hit' do
|
87
|
+
before do
|
88
|
+
Story.set("count", 0, :raw => true)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'increments the value of the cache' do
|
92
|
+
Story.incr("count", 2)
|
93
|
+
Story.get("count", :raw => true).should =~ /2/
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'returns the new cache value' do
|
97
|
+
Story.incr("count", 2).should == 2
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'when there is a cache miss' do
|
102
|
+
it 'initializes the value of the cache to the value of the block' do
|
103
|
+
Story.incr("count", 1) { 5 }
|
104
|
+
Story.get("count", :raw => true).should =~ /5/
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'returns the new cache value' do
|
108
|
+
Story.incr("count", 1) { 2 }.should == 2
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '#decr' do
|
114
|
+
describe 'when there is a cache hit' do
|
115
|
+
before do
|
116
|
+
Story.incr("count", 1) { 10 }
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'decrements the value of the cache' do
|
120
|
+
Story.decr("count", 2)
|
121
|
+
Story.get("count", :raw => true).should =~ /8/
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'returns the new cache value' do
|
125
|
+
Story.decr("count", 2).should == 8
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe 'when there is a cache miss' do
|
130
|
+
it 'initializes the value of the cache to the value of the block' do
|
131
|
+
Story.decr("count", 1) { 5 }
|
132
|
+
Story.get("count", :raw => true).should =~ /5/
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'returns the new cache value' do
|
136
|
+
Story.decr("count", 1) { 2 }.should == 2
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '#expire' do
|
142
|
+
it 'deletes the key' do
|
143
|
+
Story.set("bobo", 1)
|
144
|
+
Story.expire("bobo")
|
145
|
+
Story.get("bobo").should == nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe '#cache_key' do
|
150
|
+
it 'uses the version number' do
|
151
|
+
Story.version 1
|
152
|
+
Story.cache_key("foo").should == "Story:1/foo"
|
153
|
+
|
154
|
+
Story.version 2
|
155
|
+
Story.cache_key("foo").should == "Story:2/foo"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Cash
|
4
|
+
describe Finders do
|
5
|
+
describe 'when the cache is populated' do
|
6
|
+
describe "#find" do
|
7
|
+
describe '#find(id...)' do
|
8
|
+
describe '#find(id)' do
|
9
|
+
it "returns an active record" do
|
10
|
+
story = Story.create!(:title => 'a story')
|
11
|
+
Story.find(story.id).should == story
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'when the object is destroyed' do
|
16
|
+
describe '#find(id)' do
|
17
|
+
it "raises an error" do
|
18
|
+
story = Story.create!(:title => "I am delicious")
|
19
|
+
story.destroy
|
20
|
+
lambda { Story.find(story.id) }.should raise_error(ActiveRecord::RecordNotFound)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#find(id1, id2, ...)' do
|
26
|
+
it "returns an array" do
|
27
|
+
story1, story2 = Story.create!, Story.create!
|
28
|
+
Story.find(story1.id, story2.id).should == [story1, story2]
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#find(id, nil)" do
|
32
|
+
it "ignores the nils" do
|
33
|
+
story = Story.create!
|
34
|
+
Story.find(story.id, nil).should == story
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'when given nonexistent ids' do
|
40
|
+
describe 'when given one nonexistent id' do
|
41
|
+
it 'raises an error' do
|
42
|
+
lambda { Story.find(1) }.should raise_error(ActiveRecord::RecordNotFound)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'when given multiple nonexistent ids' do
|
47
|
+
it "raises an error" do
|
48
|
+
lambda { Story.find(1, 2, 3) }.should raise_error(ActiveRecord::RecordNotFound)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
describe '#find(nil)' do
|
54
|
+
it 'raises an error' do
|
55
|
+
lambda { Story.find(nil) }.should raise_error(ActiveRecord::RecordNotFound)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#find(object)' do
|
62
|
+
it "coerces arguments to integers" do
|
63
|
+
story = Story.create!
|
64
|
+
Story.find(story.id.to_s).should == story
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#find([...])' do
|
69
|
+
describe 'when given an array with valid ids' do
|
70
|
+
it "#finds the object with that id" do
|
71
|
+
story = Story.create!
|
72
|
+
Story.find([story.id]).should == [story]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#find([])' do
|
77
|
+
it 'returns the empty array' do
|
78
|
+
Story.find([]).should == []
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'when given nonexistent ids' do
|
83
|
+
it 'raises an error' do
|
84
|
+
lambda { Story.find([1, 2, 3]) }.should raise_error(ActiveRecord::RecordNotFound)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'when given limits and offsets' do
|
89
|
+
describe '#find([1, 2, ...], :limit => ..., :offset => ...)' do
|
90
|
+
it "returns the correct slice of objects" do
|
91
|
+
character1 = Character.create!(:name => "Sam", :story_id => 1)
|
92
|
+
character2 = Character.create!(:name => "Sam", :story_id => 1)
|
93
|
+
character3 = Character.create!(:name => "Sam", :story_id => 1)
|
94
|
+
Character.find(
|
95
|
+
[character1.id, character2.id, character3.id],
|
96
|
+
:conditions => { :name => "Sam", :story_id => 1 }, :limit => 2
|
97
|
+
).should == [character1, character2]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '#find([1], :limit => 0)' do
|
102
|
+
it "raises an error" do
|
103
|
+
character = Character.create!(:name => "Sam", :story_id => 1)
|
104
|
+
lambda do
|
105
|
+
Character.find([character.id], :conditions => { :name => "Sam", :story_id => 1 }, :limit => 0)
|
106
|
+
end.should raise_error(ActiveRecord::RecordNotFound)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe '#find(:first, ...)' do
|
113
|
+
describe '#find(:first, ..., :offset => ...)' do
|
114
|
+
it "#finds the object in the correct order" do
|
115
|
+
story1 = Story.create!(:title => 'title1')
|
116
|
+
story2 = Story.create!(:title => story1.title)
|
117
|
+
Story.find(:first, :conditions => { :title => story1.title }, :offset => 1).should == story2
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#find(:first, :conditions => [])' do
|
122
|
+
it 'finds the object in the correct order' do
|
123
|
+
story = Story.create!
|
124
|
+
Story.find(:first, :conditions => []).should == story
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "#find(:first, :conditions => '...')" do
|
129
|
+
it "coerces ruby values to the appropriate database values" do
|
130
|
+
story1 = Story.create! :title => 'a story', :published => true
|
131
|
+
story2 = Story.create! :title => 'another story', :published => false
|
132
|
+
Story.find(:first, :conditions => 'published = 0').should == story2
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#find_by_attr' do
|
139
|
+
describe '#find_by_attr(nil)' do
|
140
|
+
it 'returns nil' do
|
141
|
+
Story.find_by_id(nil).should == nil
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe 'when given non-existent ids' do
|
146
|
+
it 'returns nil' do
|
147
|
+
Story.find_by_id(-1).should == nil
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe '#find_all_by_attr' do
|
153
|
+
describe 'when given non-existent ids' do
|
154
|
+
it "does not raise an error" do
|
155
|
+
lambda { Story.find_all_by_id([-1, -2, -3]) }.should_not raise_error
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe 'when the cache is partially populated' do
|
162
|
+
describe '#find(:all, :conditions => ...)' do
|
163
|
+
it "returns the correct records" do
|
164
|
+
story1 = Story.create!(:title => title = 'once upon a time...')
|
165
|
+
$memcache.flush_all
|
166
|
+
story2 = Story.create!(:title => title)
|
167
|
+
Story.find(:all, :conditions => { :title => story1.title }).should == [story1, story2]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe '#find(id1, id2, ...)' do
|
172
|
+
it "returns the correct records" do
|
173
|
+
story1 = Story.create!(:title => 'story 1')
|
174
|
+
$memcache.flush_all
|
175
|
+
story2 = Story.create!(:title => 'story 2')
|
176
|
+
Story.find(story1.id, story2.id).should == [story1, story2]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe 'when the cache is not populated' do
|
182
|
+
describe '#find(id)' do
|
183
|
+
it "returns the correct records" do
|
184
|
+
story = Story.create!(:title => 'a story')
|
185
|
+
$memcache.flush_all
|
186
|
+
Story.find(story.id).should == story
|
187
|
+
end
|
188
|
+
|
189
|
+
it "handles after_find on model" do
|
190
|
+
class AfterFindStory < Story
|
191
|
+
def after_find
|
192
|
+
self.title
|
193
|
+
end
|
194
|
+
end
|
195
|
+
lambda do
|
196
|
+
AfterFindStory.create!(:title => 'a story')
|
197
|
+
end.should_not raise_error(ActiveRecord::MissingAttributeError)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
describe '#find(id1, id2, ...)' do
|
202
|
+
it "handles finds with multiple ids correctly" do
|
203
|
+
story1 = Story.create!
|
204
|
+
story2 = Story.create!
|
205
|
+
$memcache.flush_all
|
206
|
+
Story.find(story1.id, story2.id).should == [story1, story2]
|
207
|
+
end
|
208
|
+
end
|
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
|
223
|
+
end
|
224
|
+
end
|