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 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: