CachedSupermodel 0.1.0

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