cached_model 1.0.0

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