cached_model 1.0.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.
Files changed (5) hide show
  1. data/Manifest.txt +4 -0
  2. data/README +66 -0
  3. data/Rakefile +57 -0
  4. data/lib/cached_model.rb +190 -0
  5. metadata +56 -0
@@ -0,0 +1,4 @@
1
+ Manifest.txt
2
+ README
3
+ Rakefile
4
+ lib/cached_model.rb
data/README ADDED
@@ -0,0 +1,66 @@
1
+ = CachedModel
2
+
3
+ Rubyforge Project:
4
+
5
+ http://rubyforge.org/projects/rctools/
6
+
7
+ == About
8
+
9
+ CachedModel stores Rails ActiveRecord objects in memcache allowing for very
10
+ fast retrievals. CachedModel uses the ActiveRecord::Locking to ensure that
11
+ you don't perform multiple updates.
12
+
13
+ == Using CachedModel
14
+
15
+ First, install the cached_model gem:
16
+
17
+ $ sudo gem install cached_model
18
+
19
+ Then set up memcache-client:
20
+
21
+ $ tail -n 20 config/environments/production.rb
22
+ memcache_options = {
23
+ :c_threshold => 10_000,
24
+ :compression => true,
25
+ :debug => false,
26
+ :namespace => 'my_rails_app',
27
+ :readonly => false,
28
+ :urlencode => false
29
+ }
30
+
31
+ CACHE = MemCache.new memcache_options
32
+ CACHE.servers = 'localhost:11211'
33
+
34
+ session_options = {
35
+ :database_manager => CGI::Session::MemCacheStore,
36
+ :cache => CACHE,
37
+ :session_domain => 'trackmap.robotcoop.com'
38
+ }
39
+
40
+ ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS.update session_options
41
+
42
+ You will need separate namespaces for production and development, if using
43
+ memcache on the same machine.
44
+
45
+ Note that using memcache with tests will cause test failures, so set your
46
+ memcache to be readonly for the test environment.
47
+
48
+ Then make Rails load the gem:
49
+
50
+ $ tail -n 4 config/environment.rb
51
+ # Include your application configuration below
52
+
53
+ require_gem 'cached_model'
54
+
55
+ Then edit your ActiveRecord model to inherit from CachedModel instead of
56
+ ActiveRecord::Base:
57
+
58
+ $ head -n 8 app/models/photo.rb
59
+ ##
60
+ # A Photo from Flickr.
61
+
62
+ class Photo < CachedModel
63
+
64
+ belongs_to :point
65
+ belongs_to :route
66
+
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+
7
+ $VERBOSE = nil
8
+
9
+ spec = Gem::Specification.new do |s|
10
+ s.name = 'cached_model'
11
+ s.version = '1.0.0'
12
+ s.summary = 'An ActiveRecord::Base model that caches records'
13
+ s.authors = 'Robert Cottrell, Eric Hodel'
14
+ s.email = 'bob@robotcoop.com'
15
+
16
+ s.has_rdoc = true
17
+ s.files = File.read('Manifest.txt').split($/)
18
+ s.require_path = 'lib'
19
+
20
+ s.add_dependency 'memcache-client', '1.0.3'
21
+ end
22
+
23
+ desc 'Run tests'
24
+ task :default => [ :test ]
25
+
26
+ Rake::TestTask.new('test') do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/test_*.rb'
29
+ t.verbose = true
30
+ end
31
+
32
+ desc 'Update Manifest.txt'
33
+ task :update_manifest do
34
+ sh "find . -type f | sed -e 's%./%%' | egrep -v 'svn|swp|~' | egrep -v '^(doc|pkg)/' | sort > Manifest.txt"
35
+ end
36
+
37
+ desc 'Generate RDoc'
38
+ Rake::RDocTask.new :rdoc do |rd|
39
+ rd.rdoc_dir = 'doc'
40
+ rd.rdoc_files.add 'lib', 'README', 'LICENSE'
41
+ rd.main = 'README'
42
+ rd.options << '-d' if `which dot` =~ /\/dot/
43
+ end
44
+
45
+ desc 'Build Gem'
46
+ Rake::GemPackageTask.new spec do |pkg|
47
+ pkg.need_tar = true
48
+ end
49
+
50
+ desc 'Clean up'
51
+ task :clean => [ :clobber_rdoc, :clobber_package ]
52
+
53
+ desc 'Clean up'
54
+ task :clobber => [ :clean ]
55
+
56
+ # vim: syntax=Ruby
57
+
@@ -0,0 +1,190 @@
1
+ require 'memcache_util'
2
+
3
+ ##
4
+ # An abstract ActiveRecord descendant that caches records in memcache and in
5
+ # local memory.
6
+
7
+ class CachedModel < ActiveRecord::Base
8
+
9
+ KEY = 'active_record'
10
+ TTL = 60 * 15 # seconds
11
+
12
+ @cache_local = {}
13
+
14
+ class << self; attr_reader :cache_local; end
15
+
16
+ ##
17
+ # Override the flawed assumption ActiveRecord::Base makes about inheritance.
18
+
19
+ def self.descends_from_active_record?
20
+ superclass == CachedModel
21
+ end
22
+
23
+ ##
24
+ # Override the flawed assumption ActiveRecord::Base makes about inheritance.
25
+
26
+ def self.class_name_of_active_record_descendant(klass)
27
+ if klass.superclass == CachedModel then
28
+ return klass.name
29
+ elsif klass.superclass.nil? then
30
+ raise ActiveRecordError, "#{name} doesn't descend from ActiveRecord::Base"
31
+ else
32
+ class_name_of_active_record_descendant klass.superclass
33
+ end
34
+ end
35
+
36
+ ##
37
+ # Invalidate the cache entry for an record. The update method will
38
+ # automatically invalidate the cache when updates are made through
39
+ # ActiveRecord model record. However, several methods update tables with
40
+ # direct sql queries for effeciency. These methods should call this method
41
+ # to invalidate the cache after making those changes.
42
+ #
43
+ # NOTE - if a SQL query updates multiple rows with one query, there is
44
+ # currently no way to invalidate the affected entries unless the entire
45
+ # cache is dumped or until the TTL expires, so try not to do this.
46
+
47
+ def self.cache_delete(klass, id)
48
+ key = "#{klass}:#{id}"
49
+ CachedModel.cache_local.delete key
50
+ Cache.delete "#{KEY}:#{key}"
51
+ end
52
+
53
+ ##
54
+ # Invalidate the per-request cache. This should be called from a before
55
+ # filter at the beginning of each request.
56
+
57
+ def self.cache_reset
58
+ CachedModel.cache_local.clear
59
+ end
60
+
61
+ ##
62
+ # Override the find method to look for values in the cache before going to
63
+ # the database.
64
+
65
+ def self.find(*args)
66
+ args[0] = args.first.to_i if args.first =~ /\A\d+\Z/
67
+ # Only handle simple find requests. If the request was more complicated,
68
+ # let the base class handle it, but store the retrieved records in the
69
+ # local cache in case we need them later.
70
+ if $TESTING or args.length != 1 or not Fixnum === args.first then
71
+ records = super
72
+ if RAILS_ENV != 'test' and Array === records then
73
+ records.each { |r| r.cache_store }
74
+ end
75
+ return records
76
+ end
77
+
78
+ # Try to find the record in the local cache.
79
+ id = args.first
80
+ cache_key_local = "#{name}:#{id}"
81
+ record = CachedModel.cache_local[cache_key_local]
82
+ return record unless record.nil?
83
+
84
+ # Try to find the record in memcache and add it to the local cache
85
+ record = Cache.get "#{KEY}:#{cache_key_local}"
86
+ unless record.nil? then
87
+ CachedModel.cache_local[cache_key_local] = record
88
+ return record
89
+ end
90
+
91
+ # Fetch the record from the DB and cache it.
92
+ #
93
+ # We don't want the subsequent find_by_sql to loop back here, so guard
94
+ # the call.
95
+ #
96
+ # NOTE - this guard isn't thread safe.
97
+ begin
98
+ @skip_find_hack = true
99
+ record = super(args).first
100
+ record.cache_store
101
+ ensure
102
+ @skip_find_hack = false
103
+ end
104
+
105
+ return record
106
+ end
107
+
108
+ ##
109
+ # Skip the special handling for find by primary key if this method was
110
+ # called from find. If this is really a lookup for a single row by primary
111
+ # key, use a simple find call instead.
112
+
113
+ def self.find_by_sql(*args)
114
+ unless @skip_find_hack or $TESTING then
115
+ if args.first =~ /SELECT \* FROM #{table_name} WHERE \(#{table_name}\.#{primary_key} = '?(\d+)'?\) LIMIT 1/ then
116
+ return [self.find($1.to_i)]
117
+ end
118
+ end
119
+
120
+ return super
121
+ end
122
+
123
+ ##
124
+ # Delete the entry from the cache so that the next call goes to the database
125
+ # for the freshest copy of the record. This will also ensure that if for
126
+ # some reason a stale copy of the record was cached we can get rid of it.
127
+
128
+ def update
129
+ cache_delete
130
+ val = super
131
+ cache_store
132
+ return val
133
+ end
134
+
135
+ ##
136
+ # Delete the entry from the cache now that it isn't in the DB.
137
+
138
+ def destroy
139
+ return super
140
+ ensure
141
+ cache_delete
142
+ end
143
+
144
+ ##
145
+ # Invalidate the cache for this record before reloading from the DB.
146
+
147
+ def reload
148
+ cache_delete
149
+ return super
150
+ end
151
+
152
+ ##
153
+ # The local object cache.
154
+
155
+ def cache_local
156
+ return CachedModel.cache_local
157
+ end
158
+
159
+ ##
160
+ # Store this record in the cache.
161
+
162
+ def cache_store
163
+ cache_local[cache_key_memcache] = self
164
+ Cache.put cache_key_memcache, self, TTL
165
+ end
166
+
167
+ ##
168
+ # Remove this record from the cache.
169
+
170
+ def cache_delete
171
+ cache_local.delete cache_key_local
172
+ Cache.delete cache_key_memcache
173
+ end
174
+
175
+ ##
176
+ # The local cache key for this record.
177
+
178
+ def cache_key_local
179
+ return "#{self.class.name}:#{self.id}"
180
+ end
181
+
182
+ ##
183
+ # The memcache key for this record.
184
+
185
+ def cache_key_memcache
186
+ return "#{KEY}:#{cache_key_local}"
187
+ end
188
+
189
+ end
190
+
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11.6
3
+ specification_version: 1
4
+ name: cached_model
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2006-01-17 00:00:00 -08:00
8
+ summary: An ActiveRecord::Base model that caches records
9
+ require_paths:
10
+ - lib
11
+ email: bob@robotcoop.com
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Robert Cottrell, Eric Hodel
30
+ files:
31
+ - Manifest.txt
32
+ - README
33
+ - Rakefile
34
+ - lib/cached_model.rb
35
+ test_files: []
36
+
37
+ rdoc_options: []
38
+
39
+ extra_rdoc_files: []
40
+
41
+ executables: []
42
+
43
+ extensions: []
44
+
45
+ requirements: []
46
+
47
+ dependencies:
48
+ - !ruby/object:Gem::Dependency
49
+ name: memcache-client
50
+ version_requirement:
51
+ version_requirements: !ruby/object:Gem::Version::Requirement
52
+ requirements:
53
+ - - "="
54
+ - !ruby/object:Gem::Version
55
+ version: 1.0.3
56
+ version: