cached_model 1.1.0 → 1.2.0
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/Rakefile +2 -2
- data/lib/cached_model.rb +52 -58
- data/test/test_cached_model.rb +33 -22
- metadata +4 -4
data/Rakefile
CHANGED
@@ -8,7 +8,7 @@ $VERBOSE = nil
|
|
8
8
|
|
9
9
|
spec = Gem::Specification.new do |s|
|
10
10
|
s.name = 'cached_model'
|
11
|
-
s.version = '1.
|
11
|
+
s.version = '1.2.0'
|
12
12
|
s.summary = 'An ActiveRecord::Base model that caches records'
|
13
13
|
s.authors = 'Robert Cottrell, Eric Hodel'
|
14
14
|
s.email = 'bob@robotcoop.com'
|
@@ -17,7 +17,7 @@ spec = Gem::Specification.new do |s|
|
|
17
17
|
s.files = File.read('Manifest.txt').split($/)
|
18
18
|
s.require_path = 'lib'
|
19
19
|
|
20
|
-
s.add_dependency 'memcache-client', '1.0.3'
|
20
|
+
s.add_dependency 'memcache-client', '>= 1.0.3'
|
21
21
|
end
|
22
22
|
|
23
23
|
desc 'Run tests'
|
data/lib/cached_model.rb
CHANGED
@@ -1,11 +1,22 @@
|
|
1
|
-
$
|
1
|
+
$TESTING_CM = defined? $TESTING_CM
|
2
2
|
|
3
3
|
require 'timeout'
|
4
|
-
require 'memcache_util' unless $
|
4
|
+
require 'memcache_util' unless $TESTING_CM
|
5
5
|
|
6
6
|
##
|
7
7
|
# An abstract ActiveRecord descendant that caches records in memcache and in
|
8
8
|
# local memory.
|
9
|
+
#
|
10
|
+
# CachedModel can store into both a local in-memory cache and in memcached.
|
11
|
+
# By default memcached is enabled and the local cache is disabled.
|
12
|
+
#
|
13
|
+
# Local cache use can be enabled or disabled with
|
14
|
+
# CachedModel::use_local_cache=. If you do enable the local cache be sure to
|
15
|
+
# add a before filter that calls CachedModel::cache_reset for every request.
|
16
|
+
#
|
17
|
+
# memcached use can be enabled or disabled with CachedModel::use_memcache=.
|
18
|
+
#
|
19
|
+
# You can adjust the memcached TTL with CachedModel::ttl=
|
9
20
|
|
10
21
|
class CachedModel < ActiveRecord::Base
|
11
22
|
|
@@ -58,27 +69,25 @@ class CachedModel < ActiveRecord::Base
|
|
58
69
|
end
|
59
70
|
|
60
71
|
##
|
61
|
-
#
|
72
|
+
# We only work on 1.1.2 + because Rails broke backwards compatibility
|
73
|
+
# despite a bug http://dev.rubyonrails.org/ticket/3704
|
62
74
|
|
63
|
-
|
64
|
-
|
65
|
-
|
75
|
+
if Rails::VERSION::MAJOR > 1 or
|
76
|
+
(Rails::VERSION::MAJOR == 1 and Rails::VERSION::MINOR > 1) or
|
77
|
+
(Rails::VERSION::MAJOR == 1 and Rails::VERSION::MINOR == 1 and
|
78
|
+
Rails::VERSION::TINY >= 2) then
|
66
79
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
raise ActiveRecordError, "#{name} doesn't descend from ActiveRecord::Base"
|
75
|
-
else
|
76
|
-
class_name_of_active_record_descendant klass.superclass
|
77
|
-
end
|
80
|
+
##
|
81
|
+
# Override the flawed assumption ActiveRecord::Base makes about
|
82
|
+
# inheritance.
|
83
|
+
|
84
|
+
self.abstract_class = true
|
85
|
+
else
|
86
|
+
raise NotImplementedError, 'upgrade to Rails 1.1.2+'
|
78
87
|
end
|
79
88
|
|
80
89
|
##
|
81
|
-
# Invalidate the cache entry for
|
90
|
+
# Invalidate the cache entry for a record. The update method will
|
82
91
|
# automatically invalidate the cache when updates are made through
|
83
92
|
# ActiveRecord model record. However, several methods update tables with
|
84
93
|
# direct sql queries for effeciency. These methods should call this method
|
@@ -105,33 +114,45 @@ class CachedModel < ActiveRecord::Base
|
|
105
114
|
##
|
106
115
|
# Override the find method to look for values in the cache before going to
|
107
116
|
# the database.
|
117
|
+
#--
|
118
|
+
# TODO Push a bunch of code down into find_by_sql where it really should
|
119
|
+
# belong.
|
108
120
|
|
109
121
|
def self.find(*args)
|
110
122
|
args[0] = args.first.to_i if args.first =~ /\A\d+\Z/
|
111
123
|
# Only handle simple find requests. If the request was more complicated,
|
112
124
|
# let the base class handle it, but store the retrieved records in the
|
113
125
|
# local cache in case we need them later.
|
114
|
-
if
|
115
|
-
|
126
|
+
if args.length != 1 or not Fixnum === args.first then
|
127
|
+
# Rails requires multiple levels of indirection to look up a record
|
128
|
+
# First call super
|
116
129
|
records = super
|
117
|
-
#
|
118
|
-
return records if args.first == :all
|
130
|
+
# Then, if it was a :all, just return
|
131
|
+
return records if args.first == :all
|
119
132
|
return records if RAILS_ENV == 'test'
|
120
133
|
case records
|
121
134
|
when Array then
|
122
135
|
records.each { |r| r.cache_store }
|
123
|
-
when CachedModel then
|
124
|
-
records.cache_store # Model.find 1 gets cached here
|
125
136
|
end
|
126
137
|
return records
|
127
138
|
end
|
128
139
|
|
140
|
+
return super
|
141
|
+
end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Find by primary key from the cache.
|
145
|
+
|
146
|
+
def self.find_by_sql(*args)
|
147
|
+
return super unless args.first =~ /^SELECT \* FROM #{table_name} WHERE \(#{table_name}\.#{primary_key} = '?(\d+)'?\) +LIMIT 1/
|
148
|
+
|
149
|
+
id = $1.to_i
|
150
|
+
|
129
151
|
# Try to find the record in the local cache.
|
130
|
-
id = args.first
|
131
152
|
cache_key_local = "#{name}:#{id}"
|
132
153
|
if CachedModel.use_local_cache? then
|
133
154
|
record = CachedModel.cache_local[cache_key_local]
|
134
|
-
return record unless record.nil?
|
155
|
+
return [record] unless record.nil?
|
135
156
|
end
|
136
157
|
|
137
158
|
# Try to find the record in memcache and add it to the local cache
|
@@ -141,41 +162,14 @@ class CachedModel < ActiveRecord::Base
|
|
141
162
|
if CachedModel.use_local_cache? then
|
142
163
|
CachedModel.cache_local[cache_key_local] = record
|
143
164
|
end
|
144
|
-
return record
|
165
|
+
return [record]
|
145
166
|
end
|
146
167
|
end
|
147
168
|
|
148
|
-
# Fetch the record from the DB
|
149
|
-
|
150
|
-
#
|
151
|
-
|
152
|
-
# the call.
|
153
|
-
#
|
154
|
-
# NOTE This guard is not thread safe, beware use of CachedModel where
|
155
|
-
# ActiveRecord's thread safety is disabled.
|
156
|
-
begin
|
157
|
-
@skip_find_hack = true
|
158
|
-
record = super(args).first
|
159
|
-
ensure
|
160
|
-
@skip_find_hack = false
|
161
|
-
end
|
162
|
-
|
163
|
-
return record
|
164
|
-
end
|
165
|
-
|
166
|
-
##
|
167
|
-
# Skip the special handling for find by primary key if this method was
|
168
|
-
# called from find. If this is really a lookup for a single row by primary
|
169
|
-
# key, use a simple find call instead.
|
170
|
-
|
171
|
-
def self.find_by_sql(*args)
|
172
|
-
unless @skip_find_hack or ($TESTING and $TESTING != :cached_model) then
|
173
|
-
if args.first =~ /SELECT \* FROM #{table_name} WHERE \(#{table_name}\.#{primary_key} = '?(\d+)'?\) LIMIT 1/ then
|
174
|
-
return [find($1.to_i)]
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
return super
|
169
|
+
# Fetch the record from the DB
|
170
|
+
records = super
|
171
|
+
records.first.cache_store # only one
|
172
|
+
return records
|
179
173
|
end
|
180
174
|
|
181
175
|
##
|
data/test/test_cached_model.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
|
3
|
-
$TESTING =
|
3
|
+
$TESTING = true
|
4
|
+
$TESTING_CM = true
|
4
5
|
|
5
6
|
RAILS_ENV = 'production'
|
6
7
|
|
8
|
+
module Rails; end
|
9
|
+
module Rails::VERSION
|
10
|
+
MAJOR = 1
|
11
|
+
MINOR = 1
|
12
|
+
TINY = 2
|
13
|
+
end
|
14
|
+
|
7
15
|
module ActiveRecord; end
|
8
16
|
|
9
17
|
class ActiveRecord::Base
|
@@ -12,19 +20,31 @@ class ActiveRecord::Base
|
|
12
20
|
|
13
21
|
attr_accessor :id, :attributes
|
14
22
|
|
23
|
+
def self.abstract_class=(arg)
|
24
|
+
@abstract_class = arg
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.abstract_class?
|
28
|
+
return !!@abstract_class
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Need doco, blech
|
33
|
+
|
15
34
|
def self.find(*args)
|
16
35
|
args.flatten!
|
17
36
|
case args.first
|
18
37
|
when :first then
|
19
|
-
return find(:all, *args)
|
38
|
+
return find(:all, *args).first
|
20
39
|
when :all then
|
21
|
-
|
40
|
+
return find_by_sql("SELECT * FROM #{table_name} WHERE (#{table_name}.#{primary_key} = '#{args.last}') LIMIT 1")
|
22
41
|
else
|
23
42
|
case args.length
|
24
43
|
when 1 then
|
25
44
|
return find(:first, *args) if Fixnum === args.first
|
26
45
|
raise "Dunno what to do"
|
27
46
|
when 2 then
|
47
|
+
return find(args.first) if Hash === args.last
|
28
48
|
return new
|
29
49
|
when 3 then
|
30
50
|
return [new, new, new]
|
@@ -134,6 +154,11 @@ class TestCachedModel < Test::Unit::TestCase
|
|
134
154
|
CachedModel.use_memcache = DEFAULT_USE_MEMCACHE
|
135
155
|
end
|
136
156
|
|
157
|
+
def test_class_abstract_class_eh
|
158
|
+
assert_equal true, CachedModel.abstract_class?, 'CachedModel is abstract'
|
159
|
+
assert_equal false, Concrete.abstract_class?, 'Concrete is not abstract'
|
160
|
+
end
|
161
|
+
|
137
162
|
def test_class_cache_delete
|
138
163
|
CachedModel.use_local_cache = true
|
139
164
|
CachedModel.use_memcache = true
|
@@ -200,11 +225,6 @@ class TestCachedModel < Test::Unit::TestCase
|
|
200
225
|
assert_equal false, Cache.cache.empty?
|
201
226
|
end
|
202
227
|
|
203
|
-
def test_class_descends_from_active_record?
|
204
|
-
assert_equal true, Concrete.descends_from_active_record?
|
205
|
-
assert_equal false, STILameness.descends_from_active_record?
|
206
|
-
end
|
207
|
-
|
208
228
|
def test_class_find_complex
|
209
229
|
CachedModel.use_local_cache = true
|
210
230
|
CachedModel.use_memcache = true
|
@@ -295,23 +315,14 @@ class TestCachedModel < Test::Unit::TestCase
|
|
295
315
|
assert_equal record, Cache.cache[record.cache_key_memcache]
|
296
316
|
end
|
297
317
|
|
298
|
-
def
|
299
|
-
|
300
|
-
q = "SELECT * FROM concrete WHERE (concrete.id = #{@model.id + 1})
|
318
|
+
def test_class_find_by_sql_extra_space
|
319
|
+
CachedModel.use_local_cache = true
|
320
|
+
q = "SELECT * FROM concrete WHERE (concrete.id = #{@model.id + 1}) LIMIT 1"
|
301
321
|
record = Concrete.find_by_sql(q).first
|
302
322
|
assert_equal @model.id + 1, record.id
|
303
323
|
|
304
|
-
assert_equal
|
305
|
-
assert_equal
|
306
|
-
ensure
|
307
|
-
Concrete.instance_variable_set :@skip_find_hack, false
|
308
|
-
end
|
309
|
-
|
310
|
-
def test_class_name_of_active_record_descendant
|
311
|
-
assert_equal "Concrete",
|
312
|
-
CachedModel.class_name_of_active_record_descendant(Concrete)
|
313
|
-
assert_equal "Concrete",
|
314
|
-
CachedModel.class_name_of_active_record_descendant(STILameness)
|
324
|
+
assert_equal record, CachedModel.cache_local[record.cache_key_local]
|
325
|
+
assert_equal record, Cache.cache[record.cache_key_memcache]
|
315
326
|
end
|
316
327
|
|
317
328
|
def test_class_ttl
|
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.8.
|
2
|
+
rubygems_version: 0.8.99
|
3
3
|
specification_version: 1
|
4
4
|
name: cached_model
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2006-
|
6
|
+
version: 1.2.0
|
7
|
+
date: 2006-08-10 00:00:00 -07:00
|
8
8
|
summary: An ActiveRecord::Base model that caches records
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
version_requirement:
|
54
54
|
version_requirements: !ruby/object:Gem::Version::Requirement
|
55
55
|
requirements:
|
56
|
-
- - "
|
56
|
+
- - ">="
|
57
57
|
- !ruby/object:Gem::Version
|
58
58
|
version: 1.0.3
|
59
59
|
version:
|