Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 193
- Log:
First stage commit of Typo 4.1, modified for the ROOL site.
Includes all local modifications but a final pass needs to be
made to delete any files left over from earlier Typo versions
that shouldn't be here anymore. See the 'tags' section of the
repository for a clean Typo 4.1 tree.Note that symlinks to shared files in the RISC OS Open theme
directory have been deliberately included this time around; I
decided that on balance it was better to leave them in as
placeholders, since unlike symlinks in app/views/shared, the
Typo theme structure is not a standard Rails concept.
- Author:
- rool
- Date:
- Wed Apr 04 18:51:02 +0100 2007
- Size:
- 7386 Bytes
1 | $TESTING_CM = defined? $TESTING_CM |
2 | |
3 | require 'timeout' |
4 | require 'memcache_util' unless $TESTING_CM |
5 | |
6 | ## |
7 | # An abstract ActiveRecord descendant that caches records in memcache and in |
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= |
20 | |
21 | class CachedModel < ActiveRecord::Base |
22 | |
23 | @cache_delay_commit = {} |
24 | @cache_local = {} |
25 | @cache_transaction_level = 0 |
26 | @use_local_cache = false |
27 | @use_memcache = true |
28 | @ttl = 60 * 15 |
29 | |
30 | class << self |
31 | |
32 | # :stopdoc: |
33 | |
34 | ## |
35 | # The transaction commit buffer. You shouldn't touch me. |
36 | |
37 | attr_accessor :cache_delay_commit |
38 | |
39 | ## |
40 | # The local process cache. You shouldn't touch me. |
41 | |
42 | attr_reader :cache_local |
43 | |
44 | ## |
45 | # The transaction nesting level. You shouldn't touch me. |
46 | |
47 | attr_accessor :cache_transaction_level |
48 | |
49 | # :startdoc: |
50 | |
51 | ## |
52 | # Enables or disables use of the local cache. |
53 | # |
54 | # NOTE if you enable this you must call #cache_reset or you will |
55 | # experience uncontrollable process growth! |
56 | # |
57 | # Defaults to false. |
58 | |
59 | attr_writer :use_local_cache |
60 | |
61 | ## |
62 | # Enables or disables the use of memcache. |
63 | |
64 | attr_writer :use_memcache |
65 | |
66 | ## |
67 | # Memcache record time-to-live for stored records. |
68 | |
69 | attr_accessor :ttl |
70 | |
71 | end |
72 | |
73 | ## |
74 | # We only work on 1.1.2 + because Rails broke backwards compatibility |
75 | # despite a bug http://dev.rubyonrails.org/ticket/3704 |
76 | |
77 | if Rails::VERSION::MAJOR > 1 or |
78 | (Rails::VERSION::MAJOR == 1 and Rails::VERSION::MINOR > 1) or |
79 | (Rails::VERSION::MAJOR == 1 and Rails::VERSION::MINOR == 1 and |
80 | Rails::VERSION::TINY >= 2) then |
81 | |
82 | ## |
83 | # Override the flawed assumption ActiveRecord::Base makes about |
84 | # inheritance. |
85 | |
86 | self.abstract_class = true |
87 | else |
88 | raise NotImplementedError, 'upgrade to Rails 1.1.2+' |
89 | end |
90 | |
91 | ## |
92 | # Invalidate the cache entry for a record. The update method will |
93 | # automatically invalidate the cache when updates are made through |
94 | # ActiveRecord model record. However, several methods update tables with |
95 | # direct sql queries for effeciency. These methods should call this method |
96 | # to invalidate the cache after making those changes. |
97 | # |
98 | # NOTE - if a SQL query updates multiple rows with one query, there is |
99 | # currently no way to invalidate the affected entries unless the entire |
100 | # cache is dumped or until the TTL expires, so try not to do this. |
101 | |
102 | def self.cache_delete(klass, id) |
103 | key = "#{klass}:#{id}" |
104 | CachedModel.cache_local.delete key if CachedModel.use_local_cache? |
105 | Cache.delete "active_record:#{key}" if CachedModel.use_memcache? |
106 | end |
107 | |
108 | ## |
109 | # Invalidate the local process cache. This should be called from a before |
110 | # filter at the beginning of each request. |
111 | |
112 | def self.cache_reset |
113 | CachedModel.cache_local.clear if CachedModel.use_local_cache? |
114 | end |
115 | |
116 | ## |
117 | # Override the find method to look for values in the cache before going to |
118 | # the database. |
119 | #-- |
120 | # TODO Push a bunch of code down into find_by_sql where it really should |
121 | # belong. |
122 | |
123 | def self.find(*args) |
124 | args[0] = args.first.to_i if args.first =~ /\A\d+\Z/ |
125 | # Only handle simple find requests. If the request was more complicated, |
126 | # let the base class handle it, but store the retrieved records in the |
127 | # local cache in case we need them later. |
128 | if args.length != 1 or not Fixnum === args.first then |
129 | # Rails requires multiple levels of indirection to look up a record |
130 | # First call super |
131 | records = super |
132 | # Then, if it was a :all, just return |
133 | return records if args.first == :all |
134 | return records if RAILS_ENV == 'test' |
135 | case records |
136 | when Array then |
137 | records.each { |r| r.cache_store } |
138 | end |
139 | return records |
140 | end |
141 | |
142 | return super |
143 | end |
144 | |
145 | ## |
146 | # Find by primary key from the cache. |
147 | |
148 | def self.find_by_sql(*args) |
149 | return super unless args.first =~ /^SELECT \* FROM #{table_name} WHERE \(#{table_name}\.#{primary_key} = '?(\d+)'?\) +LIMIT 1/ |
150 | |
151 | id = $1.to_i |
152 | |
153 | # Try to find the record in the local cache. |
154 | cache_key_local = "#{name}:#{id}" |
155 | if CachedModel.use_local_cache? then |
156 | record = CachedModel.cache_local[cache_key_local] |
157 | return [record] unless record.nil? |
158 | end |
159 | |
160 | # Try to find the record in memcache and add it to the local cache |
161 | if CachedModel.use_memcache? then |
162 | record = Cache.get "active_record:#{cache_key_local}" |
163 | unless record.nil? then |
164 | if CachedModel.use_local_cache? then |
165 | CachedModel.cache_local[cache_key_local] = record |
166 | end |
167 | return [record] |
168 | end |
169 | end |
170 | |
171 | # Fetch the record from the DB |
172 | records = super |
173 | records.first.cache_store unless records.empty? # only one |
174 | return records |
175 | end |
176 | |
177 | ## |
178 | # Delay updating the cache while in a transaction. |
179 | |
180 | def self.transaction(*args) |
181 | level = CachedModel.cache_transaction_level += 1 |
182 | CachedModel.cache_delay_commit[level] = [] |
183 | |
184 | value = super |
185 | |
186 | waiting = CachedModel.cache_delay_commit.delete level |
187 | waiting.each do |obj| obj.cache_store end |
188 | |
189 | return value |
190 | ensure |
191 | CachedModel.cache_transaction_level -= 1 |
192 | end |
193 | |
194 | ## |
195 | # Returns true if use of the local cache is enabled. |
196 | |
197 | def self.use_local_cache? |
198 | return @use_local_cache |
199 | end |
200 | |
201 | ## |
202 | # Returns true if use of memcache is enabled. |
203 | |
204 | def self.use_memcache? |
205 | return @use_memcache |
206 | end |
207 | |
208 | ## |
209 | # Delete the entry from the cache now that it isn't in the DB. |
210 | |
211 | def destroy |
212 | return super |
213 | ensure |
214 | cache_delete |
215 | end |
216 | |
217 | ## |
218 | # Invalidate the cache for this record before reloading from the DB. |
219 | |
220 | def reload |
221 | cache_delete |
222 | return super |
223 | ensure |
224 | cache_store |
225 | end |
226 | |
227 | ## |
228 | # Store a new copy of ourselves into the cache. |
229 | |
230 | def update |
231 | return super |
232 | ensure |
233 | cache_store |
234 | end |
235 | |
236 | ## |
237 | # Remove this record from the cache. |
238 | |
239 | def cache_delete |
240 | cache_local.delete cache_key_local if CachedModel.use_local_cache? |
241 | Cache.delete cache_key_memcache if CachedModel.use_memcache? |
242 | end |
243 | |
244 | ## |
245 | # The local cache key for this record. |
246 | |
247 | def cache_key_local |
248 | return "#{self.class}:#{id}" |
249 | end |
250 | |
251 | ## |
252 | # The memcache key for this record. |
253 | |
254 | def cache_key_memcache |
255 | return "active_record:#{cache_key_local}" |
256 | end |
257 | |
258 | ## |
259 | # The local object cache. |
260 | |
261 | def cache_local |
262 | return CachedModel.cache_local |
263 | end |
264 | |
265 | ## |
266 | # Store this record in the cache without associations. Storing associations |
267 | # leads to wasted cache space and hard-to-debug problems. |
268 | |
269 | def cache_store |
270 | logger.info "Storing #{self} in the cache" |
271 | obj = dup |
272 | obj.send :instance_variable_set, :@attributes, attributes_before_type_cast |
273 | if CachedModel.cache_delay_commit[CachedModel.cache_transaction_level].nil? then |
274 | if CachedModel.use_local_cache? then |
275 | cache_local[cache_key_local] = obj |
276 | end |
277 | if CachedModel.use_memcache? then |
278 | Cache.put cache_key_memcache, obj, CachedModel.ttl |
279 | end |
280 | else |
281 | CachedModel.cache_delay_commit[CachedModel.cache_transaction_level] << obj |
282 | end |
283 | nil |
284 | end |
285 | |
286 | end |
287 |