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 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.1.0'
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'
@@ -1,11 +1,22 @@
1
- $TESTING = (defined? $TESTING) ? $TESTING : false
1
+ $TESTING_CM = defined? $TESTING_CM
2
2
 
3
3
  require 'timeout'
4
- require 'memcache_util' unless $TESTING == :cached_model
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
- # Override the flawed assumption ActiveRecord::Base makes about inheritance.
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
- def self.descends_from_active_record?
64
- superclass == CachedModel
65
- end
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
- # Override the flawed assumption ActiveRecord::Base makes about inheritance.
69
-
70
- def self.class_name_of_active_record_descendant(klass)
71
- if klass.superclass == CachedModel then
72
- return klass.name
73
- elsif klass.superclass.nil? then
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 an record. The update method will
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 ($TESTING and $TESTING != :cached_model) or
115
- args.length != 1 or not Fixnum === args.first then
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
- # Rails requires two levels of indirection to look up a record
118
- return records if args.first == :all and @skip_find_hack
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. Inside the mulitple levels of indirection
149
- # of find it will get cached.
150
- #
151
- # We don't want the subsequent find_by_sql to loop back here, so guard
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
  ##
@@ -1,9 +1,17 @@
1
1
  require 'test/unit'
2
2
 
3
- $TESTING = :cached_model
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
- [new]
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 test_class_find_by_sql_skip_hack
299
- Concrete.instance_variable_set :@skip_find_hack, true
300
- q = "SELECT * FROM concrete WHERE (concrete.id = #{@model.id + 1}) LIMIT 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 true, CachedModel.cache_local.empty?
305
- assert_equal true, Cache.cache.empty?
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.11.6
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.1.0
7
- date: 2006-02-19 00:00:00 -08:00
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: