cached_model 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|