CachedSupermodel 0.1.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.
@@ -0,0 +1,5 @@
1
+ == 1.0.0 / 2006-11-13
2
+
3
+ * 1 major enhancement
4
+ * Birthday!
5
+
@@ -0,0 +1,9 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ bin/cached_supermodel
6
+ lib/cached_supermodel.rb
7
+ lib/cs_active_record.rb
8
+ lib/cs_associations.rb
9
+ test/test_cached_supermodel.rb
@@ -0,0 +1,42 @@
1
+ CachedSupermodel
2
+ by Adocca AB
3
+ http://rubyforge.org/projects/adocca-plugins
4
+
5
+ == DESCRIPTION:
6
+
7
+ A library that overloads lots of methods in ActiveRecord::Base and ActiveRecord::Associations
8
+ to automatically cache all descendants of ActiveRecord::Base and their associations and finders
9
+ etc in memcached using the AdoccaMemcache gem.
10
+
11
+ == FEATURES/PROBLEMS:
12
+
13
+ * FIX (list of features or problems)
14
+
15
+ == SYNOPSYS:
16
+
17
+ FIX (code sample of usage)
18
+
19
+ == REQUIREMENTS:
20
+
21
+ * FIX (list of requirements)
22
+
23
+ == INSTALL:
24
+
25
+ * FIX (sudo gem install, anything else)
26
+
27
+ == LICENSE:
28
+
29
+ This program is free software; you can redistribute it and/or
30
+ modify it under the terms of the GNU General Public License
31
+ as published by the Free Software Foundation; either version 2
32
+ of the License, or (at your option) any later version.
33
+
34
+ This program is distributed in the hope that it will be useful,
35
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
36
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37
+ GNU General Public License for more details.
38
+
39
+ You should have received a copy of the GNU General Public License
40
+ along with this program; if not, write to the Free Software
41
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
42
+
@@ -0,0 +1,16 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/cached_supermodel.rb'
6
+
7
+ Hoe.new('CachedSupermodel', '0.1.0') do |p|
8
+ p.rubyforge_name = 'adocca-plugins'
9
+ p.summary = 'A library that automatically caches all ActiveRecord::Base instances in memcache using the AdoccaMemcache gem.'
10
+ p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
11
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
12
+ p.extra_deps << ['AdoccaMemcache', '>= 0.1.0']
13
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
14
+ end
15
+
16
+ # vim: syntax=Ruby
File without changes
@@ -0,0 +1,7 @@
1
+
2
+ $: << File.dirname(__FILE__)
3
+
4
+ require_gem 'rails'
5
+
6
+ require 'cs_active_record'
7
+ require 'cs_associations'
@@ -0,0 +1,392 @@
1
+ require 'timeout'
2
+
3
+ #
4
+ # Some monkeypatches upon ActiveRecord::Base
5
+ #
6
+ class ActiveRecord::Base
7
+ #
8
+ # Removes all association-created attributes from this instance.
9
+ #
10
+ def remove_associations!
11
+ instance_variable_set(:@attributes, attributes_before_type_cast)
12
+ instance_variables.collect do |var|
13
+ var[1..-1]
14
+ end.each do |var|
15
+ unless self.class.columns_hash.merge({"new_record" => true, "attributes" => true}).include?(var)
16
+ instance_variable_set("@#{var}", nil)
17
+ end
18
+ end
19
+ end
20
+ #
21
+ # Return a copy of this instance with all association-created attributes removed.
22
+ #
23
+ def remove_associations
24
+ obj = dup
25
+ obj.remove_associations!
26
+ obj
27
+ end
28
+ #
29
+ # Tell this class to use memcache_memoize to cache certain finders.
30
+ #
31
+ # The results of the finders will be automatically cached and reused (unless
32
+ # you give extra conditions, which will turn off the caching completely!).
33
+ #
34
+ # Invalidation will occur on save and destroy.
35
+ #
36
+ # Usage:
37
+ #
38
+ # class MyModel < ActiveRecord::Base
39
+ # cached_finders :find_by_id_and_name_and_something_else
40
+ # end
41
+ #
42
+ def self.cached_finders(*args)
43
+ args.each do |finder|
44
+ if (match = finder.to_s.match(/^find_(all_)?by_(.*)$/))
45
+ attributes = match[2].to_s.split(/_and_/)
46
+ define_method("invalidate_cached_#{finder}") do
47
+ unless self.new_record?
48
+ self.class.find(self.id).send("non_recursive_invalidate_cached_#{finder}")
49
+ end
50
+ self.send("non_recursive_invalidate_cached_#{finder}")
51
+ end
52
+ define_method("non_recursive_invalidate_cached_#{finder}") do
53
+ attribute_values = attributes.collect do |attribute|
54
+ self[attribute.to_sym]
55
+ end
56
+ expire_cached_namespace(self.class.name + ":#{finder}:" + attribute_values.inspect)
57
+ end
58
+ else
59
+ raise "Unknown finder type #{finder}"
60
+ end
61
+
62
+ class_eval "
63
+ def self.#{finder}_get_ids(*args)
64
+ match = '#{finder}'.match(/^find_(all_)?by_(.*)$/)
65
+ attributes = match[2].to_s.split(/_and_/)
66
+ normal_arguments = args[0...attributes.size]
67
+ rest = args[attributes.size..-1]
68
+ cache_value([self.name + ':#{finder}:' + normal_arguments.inspect, rest.inspect]) do
69
+ rval = method_missing(:#{finder}, *args)
70
+ if Array === rval
71
+ rval.collect(&:id)
72
+ else
73
+ rval.nil? ? nil : rval.id
74
+ end
75
+ end
76
+ end
77
+ def self.#{finder}(*args)
78
+ match = '#{finder}'.match(/^find_(all_)?by_(.*)$/)
79
+ attributes = match[2].to_s.split(/_and_/)
80
+ normal_arguments = args[0...attributes.size]
81
+ rest = args[attributes.size..-1]
82
+ extra_conditions = false
83
+ rest.each do |arg|
84
+ if Hash === arg
85
+ extra_conditions = extra_conditions || arg.include?(:conditions)
86
+ end
87
+ end
88
+ if extra_conditions
89
+ method_missing(:#{finder}, *args)
90
+ else
91
+ rval = self.#{finder}_get_ids(*args)
92
+ begin
93
+ if Array === rval
94
+ rval.collect do |oid|
95
+ self.find(oid)
96
+ end
97
+ else
98
+ rval.nil? ? nil : self.find(rval)
99
+ end
100
+ rescue ActiveRecord::RecordNotFound
101
+ nil
102
+ end
103
+ end
104
+ end
105
+ before_save :invalidate_cached_#{finder}
106
+ before_destroy :invalidate_cached_#{finder}
107
+ "
108
+ end
109
+ end
110
+
111
+ @@cache_local = {}
112
+ @@use_local_cache = false
113
+ @@ttl = 60 * 15
114
+
115
+ def self.ttl
116
+ @@ttl
117
+ end
118
+
119
+ def self.ttl=(t)
120
+ @@ttl = t
121
+ end
122
+
123
+ def self.use_local_cache?
124
+ @@use_local_cache
125
+ end
126
+
127
+ def self.use_local_cache=(u)
128
+ @@use_local_cache = u
129
+ end
130
+
131
+ ##
132
+ # Invalidate the cache entry for an record. The update method will
133
+ # automatically invalidate the cache when updates are made through
134
+ # ActiveRecord model record. However, several methods update tables with
135
+ # direct sql queries for effeciency. These methods should call this method
136
+ # to invalidate the cache after making those changes.
137
+ #
138
+ # NOTE - if a SQL query updates multiple rows with one query, there is
139
+ # currently no way to invalidate the affected entries unless the entire
140
+ # cache is dumped or until the TTL expires, so try not to do this.
141
+
142
+ def self.cache_delete(klass, id)
143
+ self.cache_local.delete self.cache_key_local(klass, id) if self.use_local_cache?
144
+ CACHE.delete self.cache_key_memcache(klass, id)
145
+ logger.debug("deleted #{self.cache_key_memcache(klass, id)}")
146
+ end
147
+
148
+ ##
149
+ # Invalidate the local process cache. This should be called from a before
150
+ # filter at the beginning of each request.
151
+
152
+ def self.cache_reset
153
+ self.cache_local.clear if self.use_local_cache?
154
+ end
155
+
156
+ def self.ok_primary_key(key)
157
+ case self.columns_hash[self.primary_key].type
158
+ when :integer
159
+ key.is_a?(Fixnum)
160
+ when :string
161
+ key.is_a?(String)
162
+ else
163
+ raise "I dont know about this column type: #{self.columns_hash[self.primary_key_column].type}"
164
+ end
165
+ end
166
+
167
+ ##
168
+ # Override the find method to look for values in the cache before going to
169
+ # the database.
170
+
171
+ class << self
172
+ alias_method :cached_supermodel_find, :find
173
+ end
174
+
175
+ def self.find(*args)
176
+ args.reject! do |arg|
177
+ arg.is_a?(Hash) && arg.values.compact.empty?
178
+ end
179
+ args[0] = args.first.to_i if args.first =~ /\A\d+\Z/
180
+ # Only handle simple find requests. If the request was more complicated,
181
+ # let the base class handle it, but store the retrieved records in the
182
+ # local cache in case we need them later.
183
+ if args.length != 1 or !ok_primary_key(args.first) then
184
+ records = cached_supermodel_find(*args)
185
+ # Rails requires two levels of indirection to look up a record
186
+ return records if args.first == :all and @skip_find_hack
187
+ case records
188
+ when Array then
189
+ records.each { |r| r.cache_store }
190
+ when ActiveRecord then
191
+ records.cache_store # Model.find 1 gets cached here
192
+ end
193
+ return records
194
+ end
195
+
196
+ # Try to find the record in the local cache.
197
+ id = args.first
198
+ if self.use_local_cache? then
199
+ record = self.cache_local[self.cache_key_local(name, id)]
200
+ return record unless record.nil?
201
+ end
202
+
203
+ # Try to find the record in memcache and add it to the local cache
204
+ record = CACHE.get self.cache_key_memcache(name, id)
205
+ unless record == :MemCache_no_such_entry then
206
+ logger.debug("found #{self.cache_key_memcache(name, id)}")
207
+ record = nil if record == :MemCache_nil
208
+ if self.use_local_cache? then
209
+ self.cache_local[self.cache_key_local(name, id)] = record
210
+ end
211
+ return record
212
+ end
213
+
214
+ # Fetch the record from the DB. Inside the multiple levels of indirection
215
+ # of find it will get cached. (no it wont, so i added a cache_store below //martin)
216
+ #
217
+ # We don't want the subsequent find_by_sql to loop back here, so guard
218
+ # the call.
219
+ #
220
+ # NOTE This guard is not thread safe, beware use of cached ActiveRecord where
221
+ # ActiveRecord's thread safety is disabled.
222
+ begin
223
+ @skip_find_hack = true
224
+ record = cached_supermodel_find(args).first
225
+ record.cache_store
226
+ ensure
227
+ @skip_find_hack = false
228
+ end
229
+
230
+ return record
231
+ end
232
+
233
+ ##
234
+ # Skip the special handling for find by primary key if this method was
235
+ # called from find. If this is really a lookup for a single row by primary
236
+ # key, use a simple find call instead.
237
+
238
+ class << self
239
+ alias_method :cached_supermodel_find_by_sql, :find_by_sql
240
+ end
241
+
242
+ def self.find_by_sql(*args)
243
+ unless @skip_find_hack
244
+ if args.first =~ /SELECT \* FROM #{table_name} WHERE \(#{table_name}\.#{primary_key} = '?(\d+)'?\) LIMIT 1/ then
245
+ return [find($1.to_i)]
246
+ end
247
+ end
248
+
249
+ return cached_supermodel_find_by_sql(*args)
250
+ end
251
+
252
+ ##
253
+ # Delete the entry from the cache now that it isn't in the DB.
254
+
255
+ alias_method :cached_supermodel_destroy, :destroy
256
+
257
+ def destroy
258
+ return cached_supermodel_destroy
259
+ ensure
260
+ cache_delete
261
+ end
262
+
263
+ ##
264
+ # Invalidate the cache for this record before reloading from the DB.
265
+
266
+ alias_method :cached_supermodel_reload, :reload
267
+
268
+ def reload
269
+ cache_delete
270
+ return cached_supermodel_reload
271
+ ensure
272
+ cache_store
273
+ end
274
+
275
+ ##
276
+ # Store a new copy of ourselves into the cache.
277
+
278
+ alias_method :cached_supermodel_update, :update
279
+
280
+ def update
281
+ return cached_supermodel_update
282
+ ensure
283
+ cache_store
284
+ end
285
+
286
+
287
+ ##
288
+ # Remove this record from the cache.
289
+
290
+ def cache_delete
291
+ cache_local.delete cache_key_local if self.class.use_local_cache?
292
+ CACHE.delete cache_key_memcache
293
+ logger.debug("deleted #{cache_key_memcache}")
294
+ end
295
+
296
+ ##
297
+ # The local cache key for this record.
298
+
299
+ def self.cache_key_local(klass, id)
300
+ return "#{klass}:#{id}"
301
+ end
302
+
303
+ def cache_key_local
304
+ self.class.cache_key_local(self.class, self.id)
305
+ end
306
+
307
+ ##
308
+ # The memcache key for this record.
309
+
310
+ def self.cache_key_memcache(klass, id)
311
+ return "active_record:#{self.cache_key_local(klass, id)}"
312
+ end
313
+
314
+ def cache_key_memcache
315
+ self.class.cache_key_memcache(self.class, self.id)
316
+ end
317
+
318
+ ##
319
+ # The local object cache.
320
+
321
+ def cache_local
322
+ return self.class.cache_local
323
+ end
324
+
325
+ ##
326
+ # Store this record in the cache without associations. Storing associations
327
+ # leads to wasted cache space and hard-to-debug problems.
328
+ def cache_store
329
+ if self.class.use_local_cache? then
330
+ cache_local[cache_key_local] = remove_associations
331
+ end
332
+ CACHE.set cache_key_memcache, remove_associations, self.class.ttl
333
+ logger.debug("stored #{cache_key_memcache}")
334
+ end
335
+
336
+ #
337
+ # Make increment_counter invalidate the cache
338
+ #
339
+
340
+ class << self
341
+ alias_method :cached_supermodel_increment_counter, :increment_counter
342
+ end
343
+
344
+ def self.increment_counter(counter_name, id)
345
+ begin
346
+ return cached_supermodel_increment_counter(counter_name, id)
347
+ ensure
348
+ cache_delete(self, id)
349
+ end
350
+ end
351
+
352
+ #
353
+ # Make decrement_counter invalidate the cache
354
+ #
355
+
356
+ class << self
357
+ alias_method :cached_supermodel_decrement_counter, :decrement_counter
358
+ end
359
+
360
+ def self.decrement_counter(counter_name, id)
361
+ begin
362
+ return cached_supermodel_decrement_counter(counter_name, id)
363
+ ensure
364
+ cache_delete(self, id)
365
+ end
366
+ end
367
+
368
+ #
369
+ # Make delete invalidate the cache
370
+ #
371
+
372
+ class << self
373
+ alias_method :cached_supermodel_delete, :delete
374
+ end
375
+
376
+ def self.delete(id)
377
+ begin
378
+ return cached_supermodel_delete(id)
379
+ ensure
380
+ if id.respond_to?(:each)
381
+ id.each do |i|
382
+ cache_delete(self.class, i)
383
+ end
384
+ else
385
+ cache_delete(self.class, id)
386
+ end
387
+ end
388
+ end
389
+
390
+
391
+ end
392
+
@@ -0,0 +1,251 @@
1
+ #
2
+ # Some monkeypatches upon ActiveRecord::Associations
3
+ #
4
+ ActiveRecord::Associations::ClassMethods.class_eval do
5
+
6
+ #
7
+ # The method that is used to define has_one and belongs_to relationships.
8
+ #
9
+ def association_accessor_methods(reflection, association_proxy_class)
10
+ #
11
+ # We only handle has_one relations
12
+ #
13
+ if reflection.macro == :has_one
14
+ #
15
+ # Add invalidations
16
+ #
17
+ our_name = name
18
+ reflection.klass.class_eval do
19
+ if reflection.options[:as]
20
+ #
21
+ # Add invalidations for poly associations.
22
+ #
23
+ type_field = reflection.primary_key_name.gsub(/_id$/, "_type")
24
+ expire_proc = Proc.new do |instance|
25
+ if instance.send(type_field) == our_name
26
+ key = "#{our_name}:#{instance.send(reflection.primary_key_name)}:#{reflection.name}"
27
+ expire_cached_value(key)
28
+ end
29
+ end
30
+ else
31
+ #
32
+ # Add invalidations for normal associations.
33
+ #
34
+ expire_proc = Proc.new do |instance|
35
+ key = "#{our_name}:#{instance.send(reflection.primary_key_name)}:#{reflection.name}"
36
+ expire_cached_value(key)
37
+ end
38
+ end
39
+
40
+ before_destroy expire_proc
41
+ before_save expire_proc
42
+
43
+ var_name = "@@association_invalidations"
44
+ class_variable_set(var_name, []) unless class_variables.include?(var_name)
45
+ current_invalidations = class_variable_get(var_name)
46
+ current_invalidations << expire_proc
47
+ class_variable_set(var_name, current_invalidations)
48
+ end
49
+
50
+ #
51
+ # Add a cached getter for the association.
52
+ #
53
+ define_method(reflection.name) do |*params|
54
+ force_reload = params.first unless params.empty?
55
+ association = instance_variable_get("@#{reflection.name}")
56
+
57
+ if association.nil? || force_reload
58
+ if new_record?
59
+ assoc = association_proxy_class.new(self, reflection)
60
+ retval = assoc.reload
61
+ association = retval.nil? ? nil : assoc
62
+ else
63
+ key = "#{self.class.name}:#{self.id}:#{reflection.name}"
64
+ expire_cached_value(key) if force_reload
65
+ association = cache_value(key) do
66
+ assoc = association_proxy_class.new(self.remove_associations, reflection)
67
+ retval = assoc.reload
68
+ retval.nil? ? nil : assoc
69
+ end
70
+ end
71
+ instance_variable_set("@#{reflection.name}", association)
72
+ end
73
+
74
+ association
75
+ end
76
+ else
77
+ #
78
+ # If this wasnt has_one, do the normal stuff.
79
+ #
80
+
81
+ define_method(reflection.name) do |*params|
82
+ force_reload = params.first unless params.empty?
83
+ association = instance_variable_get("@#{reflection.name}")
84
+
85
+ if association.nil? || force_reload
86
+ association = association_proxy_class.new(self, reflection)
87
+ retval = association.reload
88
+ unless retval.nil?
89
+ instance_variable_set("@#{reflection.name}", association)
90
+ else
91
+ instance_variable_set("@#{reflection.name}", nil)
92
+ return nil
93
+ end
94
+ end
95
+ association
96
+ end
97
+
98
+ end
99
+
100
+ #
101
+ # Here ends our customization, the rest is just copy and paste except for a few invalidations.
102
+ #
103
+
104
+ define_method("#{reflection.name}=") do |new_value|
105
+
106
+ # Here is the magic: Invalidate the current owner before we change it.
107
+ me = self
108
+ self.class.class_eval do
109
+ class_variable_get("@@association_invalidations").each do |invalidation|
110
+ invalidation.call(me)
111
+ end if class_variables.include?("@@association_invalidations")
112
+ end
113
+
114
+ association = instance_variable_get("@#{reflection.name}")
115
+ if association.nil?
116
+ association = association_proxy_class.new(self, reflection)
117
+ end
118
+
119
+ association.replace(new_value)
120
+
121
+ unless new_value.nil?
122
+ instance_variable_set("@#{reflection.name}", association)
123
+ else
124
+ instance_variable_set("@#{reflection.name}", nil)
125
+ return nil
126
+ end
127
+
128
+ association
129
+ end
130
+
131
+ define_method("set_#{reflection.name}_target") do |target|
132
+ return if target.nil?
133
+ association = association_proxy_class.new(self, reflection)
134
+ association.target = target
135
+ instance_variable_set("@#{reflection.name}", association)
136
+ end
137
+ end
138
+
139
+ #
140
+ # The method that is used to define has_many relationships.
141
+ #
142
+ # Wraps the original collection_reader_method within some memcache magic
143
+ #
144
+ def collection_reader_method(reflection, association_proxy_class)
145
+
146
+ #
147
+ # If this is a through reflection, we will invalidate on changes in the
148
+ # source reflection instead of the reflection itself.
149
+ #
150
+ invalidating_reflection = reflection
151
+ invalidating_reflection = reflection.through_reflection if (is_through_reflection = reflection.through_reflection)
152
+
153
+ #
154
+ # The reflection we invalidate on will expire our cache on destroy and save.
155
+ #
156
+ our_name = name
157
+ invalidating_reflection.klass.class_eval do
158
+ if invalidating_reflection.options[:as]
159
+ #
160
+ # Polymorphic associations should only invalidate their actual object.
161
+ #
162
+ # (only if the _type field of the association is of the right class)
163
+ #
164
+ type_field = invalidating_reflection.primary_key_name.gsub(/_id$/, '_type')
165
+ expire_proc = Proc.new do |instance|
166
+ if instance.send(type_field) == our_name
167
+ key = "#{our_name}:#{instance.send(invalidating_reflection.primary_key_name)}:#{reflection.name}"
168
+ expire_cached_value(key)
169
+ key << ":cached_count"
170
+ expire_cached_value(key)
171
+ end
172
+ end
173
+ else
174
+ #
175
+ # Non-polymorphic associations just invalidate their object regardless.
176
+ #
177
+ expire_proc = Proc.new do |instance|
178
+ key = "#{our_name}:#{instance.send(invalidating_reflection.primary_key_name)}:#{reflection.name}"
179
+ expire_cached_value(key)
180
+ key << ":cached_count"
181
+ expire_cached_value(key)
182
+ end
183
+ end
184
+ before_destroy expire_proc
185
+ before_save expire_proc
186
+
187
+ var_name = "@@association_invalidations"
188
+ class_variable_set(var_name, []) unless class_variables.include?(var_name)
189
+ current_invalidations = class_variable_get(var_name)
190
+ current_invalidations << expire_proc
191
+ class_variable_set(var_name, current_invalidations)
192
+ end
193
+
194
+ #
195
+ # We also have to add before_remove callbacks to the owner class
196
+ #
197
+ write_inheritable_array("before_remove_for_#{reflection.name}".to_sym,
198
+ [Proc.new do |owner, record|
199
+ key = "#{owner.class.name}:#{owner.id}:#{reflection.name}"
200
+ expire_cached_value(key)
201
+ key << ":cached_count"
202
+ expire_cached_value(key)
203
+ end])
204
+
205
+ class_eval "
206
+ def self.#{reflection.name}_cached_count_for(oid)
207
+ key = self.name + ':' + oid.to_s + ':#{reflection.name}:cached_count'
208
+ cache_value(key) do
209
+ self.find(oid).#{reflection.name}_cached_count
210
+ end
211
+ end
212
+ "
213
+
214
+ #
215
+ # The getter for the cached count of this association.
216
+ #
217
+ define_method("#{reflection.name}_cached_count") do |*params|
218
+ key = "#{self.class.name}:#{self.id}:#{reflection.name}:cached_count"
219
+ cache_value(key) do
220
+ association_proxy_class.new(self.remove_associations, reflection).count
221
+ end
222
+ end
223
+
224
+ #
225
+ # The getter for the association will cache its value.
226
+ #
227
+ define_method(reflection.name) do |*params|
228
+ force_reload = params.first unless params.empty?
229
+ association = instance_variable_get("@#{reflection.name}")
230
+
231
+ if !association.respond_to?(:loaded?) || force_reload
232
+ if new_record?
233
+ association = association_proxy_class.new(self, reflection)
234
+ else
235
+ key = "#{self.class.name}:#{self.id}:#{reflection.name}"
236
+ expire_cached_value(key) if force_reload
237
+ association = cache_value(key) do
238
+ a = association_proxy_class.new(self.remove_associations, reflection)
239
+ a.reload
240
+ a
241
+ end
242
+ end
243
+ instance_variable_set("@#{reflection.name}", association)
244
+ end
245
+
246
+ association
247
+ end
248
+
249
+ end
250
+
251
+ end
File without changes
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: CachedSupermodel
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-11-13 00:00:00 +01:00
8
+ summary: A library that automatically caches all ActiveRecord::Base instances in memcache using the AdoccaMemcache gem.
9
+ require_paths:
10
+ - lib
11
+ email: ryand-ruby@zenspider.com
12
+ homepage: " by Adocca AB"
13
+ rubyforge_project: adocca-plugins
14
+ description: "== FEATURES/PROBLEMS: * FIX (list of features or problems) == SYNOPSYS: FIX (code sample of usage) == REQUIREMENTS:"
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
+ - Ryan Davis
30
+ files:
31
+ - History.txt
32
+ - Manifest.txt
33
+ - README.txt
34
+ - Rakefile
35
+ - bin/cached_supermodel
36
+ - lib/cached_supermodel.rb
37
+ - lib/cs_active_record.rb
38
+ - lib/cs_associations.rb
39
+ - test/test_cached_supermodel.rb
40
+ test_files:
41
+ - test/test_cached_supermodel.rb
42
+ rdoc_options: []
43
+
44
+ extra_rdoc_files: []
45
+
46
+ executables:
47
+ - cached_supermodel
48
+ extensions: []
49
+
50
+ requirements: []
51
+
52
+ dependencies:
53
+ - !ruby/object:Gem::Dependency
54
+ name: hoe
55
+ version_requirement:
56
+ version_requirements: !ruby/object:Gem::Version::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.1.4
61
+ version:
62
+ - !ruby/object:Gem::Dependency
63
+ name: AdoccaMemcache
64
+ version_requirement:
65
+ version_requirements: !ruby/object:Gem::Version::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 0.1.0
70
+ version: