cached-models 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,379 @@
1
+ # FIXME load paths
2
+ require File.dirname(__FILE__) + '/associations/association_proxy'
3
+ require File.dirname(__FILE__) + '/associations/association_collection'
4
+ require File.dirname(__FILE__) + '/associations/has_many_association'
5
+
6
+ module ActiveRecord
7
+ module Associations
8
+ module ClassMethods
9
+ # Adds the following methods for retrieval and query of collections of associated objects:
10
+ # +collection+ is replaced with the symbol passed as the first argument, so
11
+ # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
12
+ # * <tt>collection(force_reload = false)</tt> - Returns an array of all the associated objects.
13
+ # An empty array is returned if none are found.
14
+ # * <tt>collection<<(object, ...)</tt> - Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
15
+ # * <tt>collection.delete(object, ...)</tt> - Removes one or more objects from the collection by setting their foreign keys to +NULL+.
16
+ # This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model.
17
+ # * <tt>collection=objects</tt> - Replaces the collections content by deleting and adding objects as appropriate.
18
+ # * <tt>collection_singular_ids</tt> - Returns an array of the associated objects' ids
19
+ # * <tt>collection_singular_ids=ids</tt> - Replace the collection with the objects identified by the primary keys in +ids+
20
+ # * <tt>collection.clear</tt> - Removes every object from the collection. This destroys the associated objects if they
21
+ # are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the database if <tt>:dependent => :delete_all</tt>,
22
+ # otherwise sets their foreign keys to +NULL+.
23
+ # * <tt>collection.empty?</tt> - Returns +true+ if there are no associated objects.
24
+ # * <tt>collection.size</tt> - Returns the number of associated objects.
25
+ # * <tt>collection.find</tt> - Finds an associated object according to the same rules as Base.find.
26
+ # * <tt>collection.build(attributes = {}, ...)</tt> - Returns one or more new objects of the collection type that have been instantiated
27
+ # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an
28
+ # associated object already exists, not if it's +nil+!
29
+ # * <tt>collection.create(attributes = {})</tt> - Returns a new object of the collection type that has been instantiated
30
+ # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
31
+ # *Note:* This only works if an associated object already exists, not if it's +nil+!
32
+ #
33
+ # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
34
+ # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
35
+ # * <tt>Firm#clients<<</tt>
36
+ # * <tt>Firm#clients.delete</tt>
37
+ # * <tt>Firm#clients=</tt>
38
+ # * <tt>Firm#client_ids</tt>
39
+ # * <tt>Firm#client_ids=</tt>
40
+ # * <tt>Firm#clients.clear</tt>
41
+ # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
42
+ # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
43
+ # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
44
+ # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
45
+ # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
46
+ # The declaration can also include an options hash to specialize the behavior of the association.
47
+ #
48
+ # Options are:
49
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
50
+ # from the association name. So <tt>has_many :products</tt> will by default be linked to the Product class, but
51
+ # if the real class name is SpecialProduct, you'll have to specify it with this option.
52
+ # * <tt>:conditions</tt> - Specify the conditions that the associated objects must meet in order to be included as a +WHERE+
53
+ # SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>. Record creations from the association are scoped if a hash
54
+ # is used. <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
55
+ # or <tt>@blog.posts.build</tt>.
56
+ # * <tt>:order</tt> - Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment,
57
+ # such as <tt>last_name, first_name DESC</tt>.
58
+ # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
59
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id"
60
+ # as the default <tt>:foreign_key</tt>.
61
+ # * <tt>:dependent</tt> - If set to <tt>:destroy</tt> all the associated objects are destroyed
62
+ # alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
63
+ # objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
64
+ # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. *Warning:* This option is ignored when also using
65
+ # the <tt>:through</tt> option.
66
+ # * <tt>:finder_sql</tt> - Specify a complete SQL statement to fetch the association. This is a good way to go for complex
67
+ # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
68
+ # * <tt>:counter_sql</tt> - Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
69
+ # specified but not <tt>:counter_sql</tt>, <tt>:counter_sql</tt> will be generated by replacing <tt>SELECT ... FROM</tt> with <tt>SELECT COUNT(*) FROM</tt>.
70
+ # * <tt>:extend</tt> - Specify a named module for extending the proxy. See "Association extensions".
71
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when the collection is loaded.
72
+ # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
73
+ # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
74
+ # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows.
75
+ # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if you, for example, want to do a join
76
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will rise an error.
77
+ # * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
78
+ # * <tt>:through</tt> - Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
79
+ # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
80
+ # or <tt>has_many</tt> association on the join model.
81
+ # * <tt>:source</tt> - Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
82
+ # inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
83
+ # <tt>:subscriber</tt> on Subscription, unless a <tt>:source</tt> is given.
84
+ # * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
85
+ # association is a polymorphic +belongs_to+.
86
+ # * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
87
+ # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
88
+ # * <tt>:cached</tt> - If true, all the associated objects will be cached.
89
+ #
90
+ # Option examples:
91
+ # has_many :comments, :order => "posted_on"
92
+ # has_many :comments, :include => :author
93
+ # has_many :people, :class_name => "Person", :conditions => "deleted = 0", :order => "name"
94
+ # has_many :tracks, :order => "position", :dependent => :destroy
95
+ # has_many :comments, :dependent => :nullify
96
+ # has_many :tags, :as => :taggable
97
+ # has_many :reports, :readonly => true
98
+ # has_many :posts, :cached => true
99
+ # has_many :subscribers, :through => :subscriptions, :source => :user
100
+ # has_many :subscribers, :class_name => "Person", :finder_sql =>
101
+ # 'SELECT DISTINCT people.* ' +
102
+ # 'FROM people p, post_subscriptions ps ' +
103
+ # 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
104
+ # 'ORDER BY p.first_name'
105
+ def has_many(association_id, options = {}, &extension)
106
+ reflection = create_has_many_reflection(association_id, options, &extension)
107
+
108
+ configure_dependency_for_has_many(reflection)
109
+
110
+ add_multiple_associated_save_callbacks(reflection.name)
111
+ add_association_callbacks(reflection.name, reflection.options)
112
+
113
+ if options[:through]
114
+ collection_accessor_methods(reflection, HasManyThroughAssociation, options)
115
+ else
116
+ collection_accessor_methods(reflection, HasManyAssociation, options)
117
+ end
118
+
119
+ add_cache_callbacks if options[:cached]
120
+ end
121
+
122
+ # Adds the following methods for retrieval and query for a single associated object for which this object holds an id:
123
+ # +association+ is replaced with the symbol passed as the first argument, so
124
+ # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
125
+ # * <tt>association(force_reload = false)</tt> - Returns the associated object. +nil+ is returned if none is found.
126
+ # * <tt>association=(associate)</tt> - Assigns the associate object, extracts the primary key, and sets it as the foreign key.
127
+ # * <tt>association.nil?</tt> - Returns +true+ if there is no associated object.
128
+ # * <tt>build_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
129
+ # with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
130
+ # * <tt>create_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
131
+ # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
132
+ #
133
+ # Example: A Post class declares <tt>belongs_to :author</tt>, which will add:
134
+ # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
135
+ # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
136
+ # * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
137
+ # * <tt>Post#author.nil?</tt>
138
+ # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
139
+ # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
140
+ # The declaration can also include an options hash to specialize the behavior of the association.
141
+ #
142
+ # Options are:
143
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
144
+ # from the association name. So <tt>has_one :author</tt> will by default be linked to the Author class, but
145
+ # if the real class name is Person, you'll have to specify it with this option.
146
+ # * <tt>:conditions</tt> - Specify the conditions that the associated object must meet in order to be included as a +WHERE+
147
+ # SQL fragment, such as <tt>authorized = 1</tt>.
148
+ # * <tt>:select</tt> - By default, this is <tt>*</tt> as in <tt>SELECT * FROM</tt>, but can be changed if, for example, you want to do a join
149
+ # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error.
150
+ # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
151
+ # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> association will use
152
+ # "person_id" as the default <tt>:foreign_key</tt>. Similarly, <tt>belongs_to :favorite_person, :class_name => "Person"</tt>
153
+ # will use a foreign key of "favorite_person_id".
154
+ # * <tt>:dependent</tt> - If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
155
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. This option should not be specified when
156
+ # <tt>belongs_to</tt> is used in conjunction with a <tt>has_many</tt> relationship on another class because of the potential to leave
157
+ # orphaned records behind.
158
+ # * <tt>:counter_cache</tt> - Caches the number of belonging objects on the associate class through the use of +increment_counter+
159
+ # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's
160
+ # destroyed. This requires that a column named <tt>#{table_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
161
+ # is used on the associate class (such as a Post class). You can also specify a custom counter cache column by providing
162
+ # a column name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
163
+ # When creating a counter cache column, the database statement or migration must specify a default value of <tt>0</tt>, failing to do
164
+ # this results in a counter with +NULL+ value, which will never increment.
165
+ # Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+.
166
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
167
+ # * <tt>:polymorphic</tt> - Specify this association is a polymorphic association by passing +true+.
168
+ # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
169
+ # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
170
+ # * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
171
+ # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +false+ by default.
172
+ #
173
+ # Option examples:
174
+ # belongs_to :firm, :foreign_key => "client_of"
175
+ # belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
176
+ # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
177
+ # :conditions => 'discounts > #{payments_count}'
178
+ # belongs_to :attachable, :polymorphic => true
179
+ # belongs_to :project, :readonly => true
180
+ # belongs_to :post, :counter_cache => true
181
+ # belongs_to :blog, :cached => true
182
+ def belongs_to(association_id, options = {})
183
+ reflection = create_belongs_to_reflection(association_id, options)
184
+
185
+ ivar = "@#{reflection.name}"
186
+
187
+ if reflection.options[:polymorphic]
188
+ association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
189
+
190
+ method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym
191
+ define_method(method_name) do
192
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
193
+
194
+ if association && association.target
195
+ if association.new_record?
196
+ association.save(true)
197
+ end
198
+
199
+ if association.updated?
200
+ self["#{reflection.primary_key_name}"] = association.id
201
+ self["#{reflection.options[:foreign_type]}"] = association.class.base_class.name.to_s
202
+ end
203
+ end
204
+ end
205
+ before_save method_name
206
+ else
207
+ association_accessor_methods(reflection, BelongsToAssociation)
208
+ association_constructor_method(:build, reflection, BelongsToAssociation)
209
+ association_constructor_method(:create, reflection, BelongsToAssociation)
210
+
211
+ method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym
212
+ define_method(method_name) do
213
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
214
+
215
+ if !association.nil?
216
+ if association.new_record?
217
+ association.save(true)
218
+ end
219
+
220
+ if association.updated?
221
+ self["#{reflection.primary_key_name}"] = association.id
222
+ end
223
+ end
224
+ end
225
+ before_save method_name
226
+ end
227
+
228
+ # Create the callbacks to update counter cache
229
+ if options[:counter_cache]
230
+ cache_column = options[:counter_cache] == true ?
231
+ "#{self.to_s.underscore.pluralize}_count" :
232
+ options[:counter_cache]
233
+
234
+ method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
235
+ define_method(method_name) do
236
+ association = send("#{reflection.name}")
237
+ association.class.increment_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil?
238
+ end
239
+ after_create method_name
240
+
241
+ method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
242
+ define_method(method_name) do
243
+ association = send("#{reflection.name}")
244
+ association.class.decrement_counter("#{cache_column}", send("#{reflection.primary_key_name}")) unless association.nil?
245
+ end
246
+ before_destroy method_name
247
+
248
+ module_eval(
249
+ "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)"
250
+ )
251
+ end
252
+
253
+ if options[:cached]
254
+ method_name = "belongs_to_after_save_for_#{reflection.name}".to_sym
255
+ define_method(method_name) do
256
+ send(reflection.name).expire_cache_for(self.class.name)
257
+ end
258
+ after_save method_name
259
+ end
260
+
261
+ add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
262
+
263
+ configure_dependency_for_belongs_to(reflection)
264
+ end
265
+
266
+ def collection_reader_method(reflection, association_proxy_class, options)
267
+ define_method(reflection.name) do |*params|
268
+ ivar = "@#{reflection.name}"
269
+
270
+ force_reload = params.first unless params.empty?
271
+
272
+ association = if options[:cached]
273
+ cache_read(reflection)
274
+ else
275
+ instance_variable_get(ivar) if instance_variable_defined?(ivar)
276
+ end
277
+
278
+ unless association.respond_to?(:loaded?)
279
+ association = association_proxy_class.new(self, reflection)
280
+ instance_variable_set(ivar, association)
281
+ end
282
+
283
+ if force_reload
284
+ association.reload
285
+ cache_delete(reflection) if options[:cached]
286
+ end
287
+
288
+ if options[:cached]
289
+ cache_fetch(reflection, association)
290
+ else
291
+ association
292
+ end
293
+ end
294
+
295
+ method_name = "#{reflection.name.to_s.singularize}_ids"
296
+ define_method(method_name) do
297
+ if options[:cached]
298
+ cache_fetch("#{cache_key}/#{method_name}", send("calculate_#{method_name}"))
299
+ else
300
+ send("calculate_#{method_name}")
301
+ end
302
+ end
303
+
304
+ define_method("calculate_#{method_name}") do
305
+ send(reflection.name).map { |record| record.id }
306
+ end
307
+ end
308
+
309
+ def collection_accessor_methods(reflection, association_proxy_class, options, writer = true)
310
+ collection_reader_method(reflection, association_proxy_class, options)
311
+
312
+ if writer
313
+ define_method("#{reflection.name}=") do |new_value|
314
+ # Loads proxy class instance (defined in collection_reader_method) if not already loaded
315
+ association = send(reflection.name)
316
+ association.replace(new_value)
317
+
318
+ cache_write(reflection, association) if options[:cached]
319
+
320
+ association
321
+ end
322
+
323
+ define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
324
+ ids = (new_value || []).reject { |nid| nid.blank? }
325
+ send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
326
+ end
327
+ end
328
+ end
329
+
330
+ def create_has_many_reflection(association_id, options, &extension)
331
+ options.assert_valid_keys(
332
+ :class_name, :table_name, :foreign_key, :primary_key,
333
+ :dependent,
334
+ :select, :conditions, :include, :order, :group, :limit, :offset,
335
+ :as, :through, :source, :source_type,
336
+ :uniq,
337
+ :finder_sql, :counter_sql,
338
+ :before_add, :after_add, :before_remove, :after_remove,
339
+ :extend, :readonly,
340
+ :validate, :accessible,
341
+ :cached
342
+ )
343
+
344
+ options[:extend] = create_extension_modules(association_id, extension, options[:extend])
345
+
346
+ create_reflection(:has_many, association_id, options, self)
347
+ end
348
+
349
+ def create_belongs_to_reflection(association_id, options)
350
+ options.assert_valid_keys(
351
+ :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions, :include, :dependent,
352
+ :counter_cache, :extend, :polymorphic, :readonly, :validate, :cached
353
+ )
354
+
355
+ reflection = create_reflection(:belongs_to, association_id, options, self)
356
+
357
+ if options[:polymorphic]
358
+ reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
359
+ end
360
+
361
+ reflection
362
+ end
363
+
364
+ def add_cache_callbacks
365
+ method_name = :after_save_cache_expire
366
+ return if respond_to? method_name
367
+
368
+ define_method(method_name) do
369
+ return unless self[:updated_at]
370
+
371
+ self.class.reflections.each do |name, reflection|
372
+ cache_delete(reflection) if reflection.options[:cached]
373
+ end
374
+ end
375
+ after_save method_name
376
+ end
377
+ end
378
+ end
379
+ end
@@ -0,0 +1,69 @@
1
+ module ActiveRecord
2
+ class Base
3
+ @@rails_cache = nil
4
+ cattr_accessor :rails_cache
5
+
6
+ protected
7
+ def rails_cache
8
+ self.class.rails_cache
9
+ end
10
+
11
+ # Expire the cache for the associations which contains the given class.
12
+ #
13
+ # Example:
14
+ # class Blog < ActiveRecord::Base
15
+ # has_many :posts, :cached => true
16
+ # has_many :recent_posts, :class_name => 'Post',
17
+ # :limit => 10, :order => 'id DESC', :cached => true
18
+ #
19
+ # has_many :readers, :class_name => 'Person'
20
+ # end
21
+ #
22
+ # If one of the most recent posts will be updated, #expire_cache_for
23
+ # will be invoked with the "Post" parameter, in order to expire the
24
+ # cache for the first to associations.
25
+ def expire_cache_for(class_name)
26
+ self.class.reflections.each do |name, reflection|
27
+ if reflection.options[:cached] and reflection.class_name == class_name
28
+ cache_delete(reflection)
29
+ end
30
+ end
31
+ end
32
+
33
+ def cache_read(reflection)
34
+ return unless cached_associations[reflection.name]
35
+ rails_cache.read(reflection_cache_key(reflection))
36
+ end
37
+
38
+ def cache_write(reflection, value)
39
+ cached_associations[reflection.name] = rails_cache.write(reflection_cache_key(reflection), value)
40
+ end
41
+
42
+ def cache_delete(reflection)
43
+ return unless cached_associations[reflection.name]
44
+ cached_associations[reflection.name] = !rails_cache.delete(reflection_cache_key(reflection))
45
+ end
46
+
47
+ def cache_fetch(reflection, value)
48
+ reflection_name, key = extract_options_for_cache(reflection)
49
+ cached_associations[reflection_name] = true
50
+ rails_cache.fetch(key) { value }
51
+ end
52
+
53
+ def extract_options_for_cache(reflection)
54
+ if reflection.is_a?(AssociationReflection)
55
+ [ reflection.name, reflection_cache_key(reflection) ]
56
+ else
57
+ [ reflection.split('/').last, reflection ]
58
+ end
59
+ end
60
+
61
+ def reflection_cache_key(reflection)
62
+ "#{cache_key}/#{reflection.name}"
63
+ end
64
+
65
+ def cached_associations
66
+ @cached_associations ||= {}
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,4 @@
1
+ # FIXME load paths
2
+ path = File.dirname(__FILE__)
3
+ require "#{path}/active_record/base"
4
+ require "#{path}/active_record/associations"
@@ -0,0 +1 @@
1
+ require 'cached_models'
@@ -0,0 +1,4 @@
1
+ require 'activesupport' unless defined? Rails
2
+ require File.dirname(__FILE__) + '/activerecord/lib/active_record'
3
+
4
+ ActiveRecord::Base.rails_cache = Rails.cache if defined? Rails