georgepalmer-couch_foo 0.7.1

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,1118 @@
1
+ require 'couch_foo/associations/association_proxy'
2
+ require 'couch_foo/associations/association_collection'
3
+ require 'couch_foo/associations/belongs_to_association'
4
+ require 'couch_foo/associations/belongs_to_polymorphic_association'
5
+ require 'couch_foo/associations/has_one_association'
6
+ require 'couch_foo/associations/has_many_association'
7
+ #require 'couch_foo/associations/has_many_through_association'
8
+ require 'couch_foo/associations/has_and_belongs_to_many_association'
9
+ #require 'couch_foo/associations/has_one_through_association'
10
+
11
+ module CouchFoo
12
+ class HasManyThroughAssociationNotFoundError < CouchFooError #:nodoc:
13
+ def initialize(owner_class_name, reflection)
14
+ super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
15
+ end
16
+ end
17
+
18
+ class HasManyThroughAssociationPolymorphicError < CouchFooError #:nodoc:
19
+ def initialize(owner_class_name, reflection, source_reflection)
20
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' on the polymorphic object '#{source_reflection.class_name}##{source_reflection.name}'.")
21
+ end
22
+ end
23
+
24
+ class HasManyThroughAssociationPointlessSourceTypeError < CouchFooError #:nodoc:
25
+ def initialize(owner_class_name, reflection, source_reflection)
26
+ super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' with a :source_type option if the '#{reflection.through_reflection.class_name}##{source_reflection.name}' is not polymorphic. Try removing :source_type on your association.")
27
+ end
28
+ end
29
+
30
+ class HasManyThroughSourceAssociationNotFoundError < CouchFooError #:nodoc:
31
+ def initialize(reflection)
32
+ through_reflection = reflection.through_reflection
33
+ source_reflection_names = reflection.source_reflection_names
34
+ source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
35
+ super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :connector => 'or'}?")
36
+ end
37
+ end
38
+
39
+ class HasManyThroughSourceAssociationMacroError < CouchFooError #:nodoc:
40
+ def initialize(reflection)
41
+ through_reflection = reflection.through_reflection
42
+ source_reflection = reflection.source_reflection
43
+ super("Invalid source reflection macro :#{source_reflection.macro}#{" :through" if source_reflection.options[:through]} for has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}. Use :source to specify the source reflection.")
44
+ end
45
+ end
46
+
47
+ class HasManyThroughCantAssociateThroughHasManyReflection < CouchFooError #:nodoc:
48
+ def initialize(owner, reflection)
49
+ super("Cannot modify association '#{owner.class.name}##{reflection.name}' because the source reflection class '#{reflection.source_reflection.class_name}' is associated to '#{reflection.through_reflection.class_name}' via :#{reflection.source_reflection.macro}.")
50
+ end
51
+ end
52
+ class HasManyThroughCantAssociateNewRecords < CouchFooError #:nodoc:
53
+ def initialize(owner, reflection)
54
+ super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.")
55
+ end
56
+ end
57
+
58
+ class HasManyThroughCantDissociateNewRecords < CouchFooError #:nodoc:
59
+ def initialize(owner, reflection)
60
+ super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.")
61
+ end
62
+ end
63
+
64
+ class EagerLoadPolymorphicError < CouchFooError #:nodoc:
65
+ def initialize(reflection)
66
+ super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
67
+ end
68
+ end
69
+
70
+ class ReadOnlyAssociation < CouchFooError #:nodoc:
71
+ def initialize(reflection)
72
+ super("Can not add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
73
+ end
74
+ end
75
+
76
+ module Associations # :nodoc:
77
+ def self.included(base)
78
+ base.extend(ClassMethods)
79
+ end
80
+
81
+ # Clears out the association cache
82
+ def clear_association_cache #:nodoc:
83
+ self.class.reflect_on_all_associations.to_a.each do |assoc|
84
+ instance_variable_set "@#{assoc.name}", nil
85
+ end unless self.new_record?
86
+ end
87
+
88
+ # Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
89
+ # "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
90
+ # specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own <tt>attr*</tt>
91
+ # methods. Example:
92
+ #
93
+ # class Project < CouchFoo::Base
94
+ # belongs_to :portfolio
95
+ # has_one :project_manager
96
+ # has_many :milestones
97
+ # has_and_belongs_to_many :categories
98
+ # end
99
+ #
100
+ # The project class now has the following methods (and more) to ease the traversal and manipulation of its relationships:
101
+ # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
102
+ # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
103
+ # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
104
+ # <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.find(:all, options),</tt>
105
+ # <tt>Project#milestones.build, Project#milestones.create</tt>
106
+ # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
107
+ # <tt>Project#categories.delete(category1)</tt>
108
+ #
109
+ # === Note
110
+ #
111
+ # The current CouchFoo implementation does not include has_and_belongs_to_many This will be added
112
+ # in a future release along with an option for using has_many in an inline context, so all the
113
+ # associated documents are stored in the parent itself rather than in separate records.
114
+ #
115
+ # === A word of warning
116
+ #
117
+ # Don't create associations that have the same name as instance methods of CouchFoo::Base. Since the association
118
+ # adds a method with that name to its model, it will override the inherited method and break things.
119
+ # For instance, +attributes+ and +connection+ would be bad choices for association names.
120
+ #
121
+ # == Auto-generated methods
122
+ #
123
+ # === Singular associations (one-to-one)
124
+ # | | belongs_to |
125
+ # generated methods | belongs_to | :polymorphic | has_one
126
+ # ----------------------------------+------------+--------------+---------
127
+ # #other | X | X | X
128
+ # #other=(other) | X | X | X
129
+ # #build_other(attributes={}) | X | | X
130
+ # #create_other(attributes={}) | X | | X
131
+ # #other.create!(attributes={}) | | | X
132
+ # #other.nil? | X | X |
133
+ #
134
+ # ===Collection associations (one-to-many / many-to-many)
135
+ # | | | has_many
136
+ # generated methods | habtm | has_many | :through
137
+ # ----------------------------------+-------+----------+----------
138
+ # #others | X | X | X
139
+ # #others=(other,other,...) | X | X | X
140
+ # #other_ids | X | X | X
141
+ # #other_ids=(id,id,...) | X | X | X
142
+ # #others<< | X | X | X
143
+ # #others.push | X | X | X
144
+ # #others.concat | X | X | X
145
+ # #others.build(attributes={}) | X | X | X
146
+ # #others.create(attributes={}) | X | X | X
147
+ # #others.create!(attributes={}) | X | X | X
148
+ # #others.size | X | X | X
149
+ # #others.length | X | X | X
150
+ # #others.count | X | X | X
151
+ # #others.sum(args*,&block) | X | X | X
152
+ # #others.empty? | X | X | X
153
+ # #others.clear | X | X | X
154
+ # #others.delete(other,other,...) | X | X | X
155
+ # #others.delete_all | X | X |
156
+ # #others.destroy_all | X | X | X
157
+ # #others.find(*args) | X | X | X
158
+ # #others.find_first | X | |
159
+ # #others.uniq | X | X | X
160
+ # #others.reset | X | X | X
161
+ #
162
+ # == Cardinality and associations
163
+ #
164
+ # Couch Foo associations can be used to describe one-to-one, one-to-many and many-to-many
165
+ # relationships between models. Each model uses an association to describe its role in
166
+ # the relation. The +belongs_to+ association is always used in the model that has
167
+ # the foreign key.
168
+ #
169
+ # === One-to-one
170
+ #
171
+ # Use +has_one+ in the base, and +belongs_to+ in the associated model.
172
+ #
173
+ # class Employee < CouchFoo::Base
174
+ # has_one :office
175
+ # end
176
+ # class Office < CouchFoo::Base
177
+ # belongs_to :employee # foreign key - employee_id
178
+ # end
179
+ #
180
+ # === One-to-many
181
+ #
182
+ # Use +has_many+ in the base, and +belongs_to+ in the associated model.
183
+ #
184
+ # class Manager < CouchFoo::Base
185
+ # has_many :employees
186
+ # end
187
+ # class Employee < CouchFoo::Base
188
+ # belongs_to :manager # foreign key - manager_id
189
+ # end
190
+ #
191
+ # === Many-to-many
192
+ #
193
+ # Not implement yet
194
+ #
195
+ # == Is it a +belongs_to+ or +has_one+ association?
196
+ #
197
+ # Both express a 1-1 relationship. The difference is mostly where to place the foreign key, which goes on the model for the class
198
+ # declaring the +belongs_to+ relationship. Example:
199
+ #
200
+ # class User < CouchFoo::Base
201
+ # # I reference an account.
202
+ # belongs_to :account
203
+ # end
204
+ #
205
+ # class Account < CouchFoo::Base
206
+ # # One user references me.
207
+ # has_one :user
208
+ # end
209
+ #
210
+ # The properties definitions for these classes could look something like:
211
+ # class User < CouchFoo::Base
212
+ # property :account_id, Integer
213
+ # property :name, String
214
+ # end
215
+ #
216
+ # class Account < CouchFoo::Base
217
+ # property :name, String
218
+ # end
219
+ #
220
+ # == Unsaved objects and associations
221
+ #
222
+ # You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
223
+ # aware of, mostly involving the saving of associated objects.
224
+ #
225
+ # === One-to-one associations
226
+ #
227
+ # * Assigning an object to a +has_one+ association automatically saves that object and the object being replaced (if there is one), in
228
+ # order to update their primary keys - except if the parent object is unsaved (<tt>new_record? == true</tt>).
229
+ # * If either of these saves fail (due to one of the objects being invalid) the assignment statement returns +false+ and the assignment
230
+ # is cancelled.
231
+ # * If you wish to assign an object to a +has_one+ association without saving it, use the <tt>association.build</tt> method (documented below).
232
+ # * Assigning an object to a +belongs_to+ association does not save the object, since the foreign key field belongs on the parent. It
233
+ # does not save the parent either.
234
+ #
235
+ # === Collections
236
+ #
237
+ # * Adding an object to a collection (+has_many+ or +has_and_belongs_to_many+) automatically saves that object, except if the parent object
238
+ # (the owner of the collection) is not yet stored in the database.
239
+ # * If saving any of the objects being added to a collection (via <tt>push</tt> or similar) fails, then <tt>push</tt> returns +false+.
240
+ # * You can add an object to a collection without automatically saving it by using the <tt>collection.build</tt> method (documented below).
241
+ # * All unsaved (<tt>new_record? == true</tt>) members of the collection are automatically saved when the parent is saved.
242
+ #
243
+ # === Association callbacks
244
+ #
245
+ # Similar to the normal callbacks that hook into the lifecycle of an Couch Foo object, you can also define callbacks that get
246
+ # triggered when you add an object to or remove an object from an association collection. Example:
247
+ #
248
+ # class Project
249
+ # has_and_belongs_to_many :developers, :after_add => :evaluate_velocity
250
+ #
251
+ # def evaluate_velocity(developer)
252
+ # ...
253
+ # end
254
+ # end
255
+ #
256
+ # It's possible to stack callbacks by passing them as an array. Example:
257
+ #
258
+ # class Project
259
+ # has_and_belongs_to_many :developers, :after_add => [:evaluate_velocity, Proc.new { |p, d| p.shipping_date = Time.now}]
260
+ # end
261
+ #
262
+ # Possible callbacks are: +before_add+, +after_add+, +before_remove+ and +after_remove+.
263
+ #
264
+ # Should any of the +before_add+ callbacks throw an exception, the object does not get added to the collection. Same with
265
+ # the +before_remove+ callbacks; if an exception is thrown the object doesn't get removed.
266
+ #
267
+ # === Association extensions
268
+ #
269
+ # The proxy objects that control the access to associations can be extended through anonymous modules. This is especially
270
+ # beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this association.
271
+ # Example:
272
+ #
273
+ # class Account < CouchFoo::Base
274
+ # has_many :people do
275
+ # def find_or_create_by_name(name)
276
+ # first_name, last_name = name.split(" ", 2)
277
+ # find_or_create_by_first_name_and_last_name(first_name, last_name)
278
+ # end
279
+ # end
280
+ # end
281
+ #
282
+ # person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
283
+ # person.first_name # => "David"
284
+ # person.last_name # => "Heinemeier Hansson"
285
+ #
286
+ # If you need to share the same extensions between many associations, you can use a named extension module. Example:
287
+ #
288
+ # module FindOrCreateByNameExtension
289
+ # def find_or_create_by_name(name)
290
+ # first_name, last_name = name.split(" ", 2)
291
+ # find_or_create_by_first_name_and_last_name(first_name, last_name)
292
+ # end
293
+ # end
294
+ #
295
+ # class Account < CouchFoo::Base
296
+ # has_many :people, :extend => FindOrCreateByNameExtension
297
+ # end
298
+ #
299
+ # class Company < CouchFoo::Base
300
+ # has_many :people, :extend => FindOrCreateByNameExtension
301
+ # end
302
+ #
303
+ # If you need to use multiple named extension modules, you can specify an array of modules with the <tt>:extend</tt> option.
304
+ # In the case of name conflicts between methods in the modules, methods in modules later in the array supercede
305
+ # those earlier in the array. Example:
306
+ #
307
+ # class Account < CouchFoo::Base
308
+ # has_many :people, :extend => [FindOrCreateByNameExtension, FindRecentExtension]
309
+ # end
310
+ #
311
+ # Some extensions can only be made to work with knowledge of the association proxy's internals.
312
+ # Extensions can access relevant state using accessors on the association proxy:
313
+ #
314
+ # * +proxy_owner+ - Returns the object the association is part of.
315
+ # * +proxy_reflection+ - Returns the reflection object that describes the association.
316
+ # * +proxy_target+ - Returns the associated object for +belongs_to+ and +has_one+, or the collection of associated objects for +has_many+ and +has_and_belongs_to_many+.
317
+ #
318
+ # === Association Join Models
319
+ #
320
+ # This is not supported yet
321
+ #
322
+ # === Polymorphic Associations
323
+ #
324
+ # Polymorphic associations on models are not restricted on what types of models they can be associated with. Rather, they
325
+ # specify an interface that a +has_many+ association must adhere to.
326
+ #
327
+ # class Asset < CouchFoo::Base
328
+ # belongs_to :attachable, :polymorphic => true
329
+ # end
330
+ #
331
+ # class Post < CouchFoo::Base
332
+ # has_many :assets, :as => :attachable # The :as option specifies the polymorphic interface to use.
333
+ # end
334
+ #
335
+ # @asset.attachable = @post
336
+ #
337
+ # This works by using a type property in addition to a foreign key to specify the associated record. In the Asset example, you'd need
338
+ # an +attachable_id+ key attribute and an +attachable_type+ string attribute.
339
+ #
340
+ # Using polymorphic associations in combination with inheritance is a little tricky. In order
341
+ # for the associations to work as expected, ensure that you store the base model in the
342
+ # type property of the polymorphic association. To continue with the asset example above, suppose
343
+ # there are guest posts and member posts that use inheritence. In this case, there must be a +type+
344
+ # property in the Post model.
345
+ #
346
+ # class Asset < CouchFoo::Base
347
+ # belongs_to :attachable, :polymorphic => true
348
+ #
349
+ # def attachable_type=(sType)
350
+ # super(sType.to_s.classify.constantize.class.to_s)
351
+ # end
352
+ # end
353
+ #
354
+ # class Post < CouchFoo::Base
355
+ # # because we store "Post" in attachable_type now :dependent => :destroy will work
356
+ # has_many :assets, :as => :attachable, :dependent => :destroy
357
+ # end
358
+ #
359
+ # class GuestPost < Post
360
+ # end
361
+ #
362
+ # class MemberPost < Post
363
+ # end
364
+ #
365
+ # == Caching
366
+ #
367
+ # All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
368
+ # instructed not to. The cache is even shared across methods to make it even cheaper to use the macro-added methods without
369
+ # worrying too much about performance at the first go. Example:
370
+ #
371
+ # project.milestones # fetches milestones from the database
372
+ # project.milestones.size # uses the milestone cache
373
+ # project.milestones.empty? # uses the milestone cache
374
+ # project.milestones(true).size # fetches milestones from the database
375
+ # project.milestones # uses the milestone cache
376
+ #
377
+ # == Eager loading of associations
378
+ #
379
+ # Not implemented yet
380
+ #
381
+ # == Modules
382
+ #
383
+ # By default, associations will look for objects within the current module scope. Consider:
384
+ #
385
+ # module MyApplication
386
+ # module Business
387
+ # class Firm < CouchFoo::Base
388
+ # has_many :clients
389
+ # end
390
+ #
391
+ # class Company < CouchFoo::Base; end
392
+ # end
393
+ # end
394
+ #
395
+ # When Firm#clients is called, it will in turn call <tt>MyApplication::Business::Company.find(firm.id)</tt>. If you want to associate
396
+ # with a class in another module scope, this can be done by specifying the complete class name. Example:
397
+ #
398
+ # module MyApplication
399
+ # module Business
400
+ # class Firm < CouchFoo::Base; end
401
+ # end
402
+ #
403
+ # module Billing
404
+ # class Account < CouchFoo::Base
405
+ # belongs_to :firm, :class_name => "MyApplication::Business::Firm"
406
+ # end
407
+ # end
408
+ # end
409
+ #
410
+ # == Type safety with <tt>CouchFoo::AssociationTypeMismatch</tt>
411
+ #
412
+ # If you attempt to assign an object to an association that doesn't match the inferred or specified <tt>:class_name</tt>, you'll
413
+ # get an <tt>CouchFoo::AssociationTypeMismatch</tt>.
414
+ #
415
+ # == Options
416
+ #
417
+ # All of the association macros can be specialized through options. This makes cases more complex than the simple and guessable ones
418
+ # possible.
419
+ module ClassMethods
420
+ # Adds the following methods for retrieval and query of collections of associated objects:
421
+ # +collection+ is replaced with the symbol passed as the first argument, so
422
+ # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>.
423
+ # * <tt>collection(force_reload = false)</tt> - Returns an array of all the associated objects.
424
+ # An empty array is returned if none are found.
425
+ # * <tt>collection<<(object, ...)</tt> - Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
426
+ # * <tt>collection.delete(object, ...)</tt> - Removes one or more objects from the collection by setting their foreign keys to +NULL+.
427
+ # This will also destroy the objects if they're declared as +belongs_to+ and dependent on this model.
428
+ # * <tt>collection=objects</tt> - Replaces the collections content by deleting and adding objects as appropriate.
429
+ # * <tt>collection_singular_ids</tt> - Returns an array of the associated objects' ids
430
+ # * <tt>collection_singular_ids=ids</tt> - Replace the collection with the objects identified by the primary keys in +ids+
431
+ # * <tt>collection.clear</tt> - Removes every object from the collection. This destroys the associated objects if they
432
+ # are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the database if <tt>:dependent => :delete_all</tt>,
433
+ # otherwise sets their foreign keys to +NULL+.
434
+ # * <tt>collection.empty?</tt> - Returns +true+ if there are no associated objects.
435
+ # * <tt>collection.size</tt> - Returns the number of associated objects.
436
+ # * <tt>collection.find</tt> - Finds an associated object according to the same rules as Base.find.
437
+ # * <tt>collection.build(attributes = {}, ...)</tt> - Returns one or more new objects of the collection type that have been instantiated
438
+ # with +attributes+ and linked to this object through a foreign key, but have not yet been saved. *Note:* This only works if an
439
+ # associated object already exists, not if it's +nil+!
440
+ # * <tt>collection.create(attributes = {})</tt> - Returns a new object of the collection type that has been instantiated
441
+ # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
442
+ # *Note:* This only works if an associated object already exists, not if it's +nil+!
443
+ #
444
+ # Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
445
+ # * <tt>Firm#clients</tt> (similar to <tt>Clients.find :all, :conditions => "firm_id = #{id}"</tt>)
446
+ # * <tt>Firm#clients<<</tt>
447
+ # * <tt>Firm#clients.delete</tt>
448
+ # * <tt>Firm#clients=</tt>
449
+ # * <tt>Firm#client_ids</tt>
450
+ # * <tt>Firm#client_ids=</tt>
451
+ # * <tt>Firm#clients.clear</tt>
452
+ # * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
453
+ # * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
454
+ # * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
455
+ # * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
456
+ # * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
457
+ # The declaration can also include an options hash to specialize the behavior of the association.
458
+ #
459
+ # Options are:
460
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
461
+ # from the association name. So <tt>has_many :products</tt> will by default be linked to the Product class, but
462
+ # if the real class name is SpecialProduct, you'll have to specify it with this option.
463
+ # * <tt>:conditions</tt> - Specify the conditions that the associated objects must meet in order to be included
464
+ # in the results. For example <tt>has_many :posts, :conditions => {:published => true}</tt>. This will also
465
+ # create published posts with <tt>@blog.posts.create</tt> or <tt>@blog.posts.build</tt>.
466
+ # * <tt>:order</tt> - Specify the order in which the associated objects are returned by a property to sort on,
467
+ # for example :order => :product_weight. See notes in CouchFoo#find when using with :limit
468
+ # * <tt>:dependent</tt> - If set to <tt>:destroy</tt> all the associated objects are destroyed
469
+ # alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
470
+ # objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
471
+ # objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. *Warning:* This option is ignored when also using
472
+ # the <tt>:through</tt> option.
473
+ # * <tt>:extend</tt> - Specify a named module for extending the proxy. See "Association extensions".
474
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when the collection is loaded.
475
+ # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned. See notes
476
+ # in CouchFoo#find when using with :order
477
+ # * <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.
478
+ # * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
479
+ # * <tt>:through</tt> - Not implemented at the moment
480
+ # * <tt>:source_type</tt> - Specifies type of the source association used by <tt>has_many :through</tt> queries where the source
481
+ # association is a polymorphic +belongs_to+.
482
+ # * <tt>:uniq</tt> - If true, duplicates will be omitted from the collection. Useful in conjunction with <tt>:through</tt>.
483
+ # * <tt>:readonly</tt> - If true, all the associated objects are readonly through the association.
484
+ # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. true by default.
485
+ #
486
+ # Option examples:
487
+ # has_many :comments, :order => :posted_on
488
+ # has_many :comments, :include => :author
489
+ # has_many :people, :class_name => "Person", :conditions => {deleted => 0}, :order => "name"
490
+ # has_many :tracks, :order => :position, :dependent => :destroy
491
+ # has_many :comments, :dependent => :nullify
492
+ # has_many :tags, :as => :taggable
493
+ # has_many :reports, :readonly => true
494
+ def has_many(association_id, options = {}, &extension)
495
+ reflection = create_has_many_reflection(association_id, options, &extension)
496
+ configure_dependency_for_has_many(reflection)
497
+
498
+ add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
499
+ add_multiple_associated_save_callbacks(reflection.name)
500
+ add_association_callbacks(reflection.name, reflection.options)
501
+
502
+ #if options[:through]
503
+ # collection_accessor_methods(reflection, HasManyThroughAssociation)
504
+ #else
505
+ collection_accessor_methods(reflection, HasManyAssociation)
506
+ #end
507
+ end
508
+
509
+ # Adds the following methods for retrieval and query of a single associated object:
510
+ # +association+ is replaced with the symbol passed as the first argument, so
511
+ # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>.
512
+ # * <tt>association(force_reload = false)</tt> - Returns the associated object. +nil+ is returned if none is found.
513
+ # * <tt>association=(associate)</tt> - Assigns the associate object, extracts the primary key, sets it as the foreign key,
514
+ # and saves the associate object.
515
+ # * <tt>association.nil?</tt> - Returns +true+ if there is no associated object.
516
+ # * <tt>build_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
517
+ # with +attributes+ and linked to this object through a foreign key, but has not yet been saved. Note: This ONLY works if
518
+ # an association already exists. It will NOT work if the association is +nil+.
519
+ # * <tt>create_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
520
+ # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
521
+ #
522
+ # Example: An Account class declares <tt>has_one :beneficiary</tt>, which will add:
523
+ # * <tt>Account#beneficiary</tt> (similar to <tt>Beneficiary.find(:first, :conditions => "account_id = #{id}")</tt>)
524
+ # * <tt>Account#beneficiary=(beneficiary)</tt> (similar to <tt>beneficiary.account_id = account.id; beneficiary.save</tt>)
525
+ # * <tt>Account#beneficiary.nil?</tt>
526
+ # * <tt>Account#build_beneficiary</tt> (similar to <tt>Beneficiary.new("account_id" => id)</tt>)
527
+ # * <tt>Account#create_beneficiary</tt> (similar to <tt>b = Beneficiary.new("account_id" => id); b.save; b</tt>)
528
+ #
529
+ # The declaration can also include an options hash to specialize the behavior of the association.
530
+ #
531
+ # Options are:
532
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
533
+ # from the association name. So <tt>has_one :manager</tt> will by default be linked to the Manager class, but
534
+ # if the real class name is Person, you'll have to specify it with this option.
535
+ # * <tt>:conditions</tt> - Specify the conditions that the associated objects must meet in order to be included
536
+ # in the results. For example <tt>has_many :posts, :conditions => {:published => true}</tt>. This will also
537
+ # create published posts with <tt>@blog.posts.create</tt> or <tt>@blog.posts.build</tt>.
538
+ # * <tt>:order</tt> - Specify the order in which the associated objects are returned by a property to sort on,
539
+ # for example :order => :product_weight. See notes in CouchFoo#find when using with :limit
540
+ # * <tt>:dependent</tt> - If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
541
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. If set to <tt>:nullify</tt>, the associated
542
+ # object's foreign key is set to +NULL+. Also, association is assigned.
543
+ # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
544
+ # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id"
545
+ # as the default <tt>:foreign_key</tt>.
546
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
547
+ # * <tt>:as</tt> - Specifies a polymorphic interface (See <tt>belongs_to</tt>).
548
+ # * <tt>:through</tt> - Not implemented yet
549
+ # * <tt>:source</tt> - Not implemented yet
550
+ # * <tt>:source_type</tt> - Not implemented yet
551
+ # * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
552
+ # * <tt>:validate</tt> - If false, don't validate the associated object when saving the parent object. +false+ by default.
553
+ #
554
+ # Option examples:
555
+ # has_one :credit_card, :dependent => :destroy # destroys the associated credit card
556
+ # has_one :credit_card, :dependent => :nullify # updates the associated records foreign key value to NULL rather than destroying it
557
+ # has_one :last_comment, :class_name => "Comment", :order => :posted_on
558
+ # has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
559
+ # has_one :attachment, :as => :attachable
560
+ # has_one :boss, :readonly => :true
561
+ def has_one(association_id, options = {})
562
+ #if options[:through]
563
+ # reflection = create_has_one_through_reflection(association_id, options)
564
+ # association_accessor_methods(reflection, CouchFoo::Associations::HasOneThroughAssociation)
565
+ #else
566
+ reflection = create_has_one_reflection(association_id, options)
567
+
568
+ ivar = "@#{reflection.name}"
569
+
570
+ method_name = "has_one_after_save_for_#{reflection.name}".to_sym
571
+ define_method(method_name) do
572
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
573
+
574
+ if !association.nil? && (new_record? || association.new_record? || association["#{reflection.primary_key_name}"] != id)
575
+ association["#{reflection.primary_key_name}"] = id
576
+ association.save(true)
577
+ end
578
+ end
579
+ after_save method_name
580
+
581
+ add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
582
+ association_accessor_methods(reflection, HasOneAssociation)
583
+ association_constructor_method(:build, reflection, HasOneAssociation)
584
+ association_constructor_method(:create, reflection, HasOneAssociation)
585
+
586
+ configure_dependency_for_has_one(reflection)
587
+ #end
588
+ end
589
+
590
+ # Adds the following methods for retrieval and query for a single associated object for which this object holds an id:
591
+ # +association+ is replaced with the symbol passed as the first argument, so
592
+ # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>.
593
+ # * <tt>association(force_reload = false)</tt> - Returns the associated object. +nil+ is returned if none is found.
594
+ # * <tt>association=(associate)</tt> - Assigns the associate object, extracts the primary key, and sets it as the foreign key.
595
+ # * <tt>association.nil?</tt> - Returns +true+ if there is no associated object.
596
+ # * <tt>build_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
597
+ # with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
598
+ # * <tt>create_association(attributes = {})</tt> - Returns a new object of the associated type that has been instantiated
599
+ # with +attributes+, linked to this object through a foreign key, and that has already been saved (if it passed the validation).
600
+ #
601
+ # Example: A Post class declares <tt>belongs_to :author</tt>, which will add:
602
+ # * <tt>Post#author</tt> (similar to <tt>Author.find(author_id)</tt>)
603
+ # * <tt>Post#author=(author)</tt> (similar to <tt>post.author_id = author.id</tt>)
604
+ # * <tt>Post#author?</tt> (similar to <tt>post.author == some_author</tt>)
605
+ # * <tt>Post#author.nil?</tt>
606
+ # * <tt>Post#build_author</tt> (similar to <tt>post.author = Author.new</tt>)
607
+ # * <tt>Post#create_author</tt> (similar to <tt>post.author = Author.new; post.author.save; post.author</tt>)
608
+ # The declaration can also include an options hash to specialize the behavior of the association.
609
+ #
610
+ # Options are:
611
+ # * <tt>:class_name</tt> - Specify the class name of the association. Use it only if that name can't be inferred
612
+ # from the association name. So <tt>has_one :author</tt> will by default be linked to the Author class, but
613
+ # if the real class name is Person, you'll have to specify it with this option.
614
+ # * <tt>:conditions</tt> - Specify the conditions that the associated objects must meet in order to be included
615
+ # in the results. For example <tt>has_many :posts, :conditions => {:published => true}</tt>. This will also
616
+ # create published posts with <tt>@blog.posts.create</tt> or <tt>@blog.posts.build</tt>.
617
+ # * <tt>:foreign_key</tt> - Specify the foreign key used for the association. By default this is guessed to be the name
618
+ # of the association with an "_id" suffix. So a class that defines a <tt>belongs_to :person</tt> association will use
619
+ # "person_id" as the default <tt>:foreign_key</tt>. Similarly, <tt>belongs_to :favorite_person, :class_name => "Person"</tt>
620
+ # will use a foreign key of "favorite_person_id".
621
+ # * <tt>:dependent</tt> - If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
622
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. This option should not be specified when
623
+ # <tt>belongs_to</tt> is used in conjunction with a <tt>has_many</tt> relationship on another class because of the potential to leave
624
+ # orphaned records behind.
625
+ # * <tt>:counter_cache</tt> - Caches the number of belonging objects on the associate class through the use of +increment_counter+
626
+ # and +decrement_counter+. The counter cache is incremented when an object of this class is created and decremented when it's
627
+ # destroyed. This requires that a property named <tt>#{document_name}_count</tt> (such as +comments_count+ for a belonging Comment class)
628
+ # is used on the associate class (such as a Post class). You can also specify a custom counter cache property by providing
629
+ # a property name instead of a +true+/+false+ value to this option (e.g., <tt>:counter_cache => :my_custom_counter</tt>.)
630
+ # When creating a counter cache property, the database statement or migration must specify a default value of <tt>0</tt>, failing to do
631
+ # this results in a counter with +NULL+ value, which will never increment.
632
+ # Note: Specifying a counter cache will add it to that model's list of readonly attributes using +attr_readonly+.
633
+ # * <tt>:include</tt> - Specify second-order associations that should be eager loaded when this object is loaded.
634
+ # * <tt>:polymorphic</tt> - Specify this association is a polymorphic association by passing +true+.
635
+ # Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
636
+ # to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
637
+ # * <tt>:readonly</tt> - If true, the associated object is readonly through the association.
638
+ # * <tt>:validate</tt> - If false, don't validate the associated objects when saving the parent object. +false+ by default.
639
+ #
640
+ # Option examples:
641
+ # belongs_to :firm, :foreign_key => "client_of"
642
+ # belongs_to :author, :class_name => "Person", :foreign_key => "author_id"
643
+ # belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
644
+ # :conditions => {discounts = #{payments_count}}
645
+ # belongs_to :attachable, :polymorphic => true
646
+ # belongs_to :project, :readonly => true
647
+ # belongs_to :post, :counter_cache => true
648
+ def belongs_to(association_id, options = {})
649
+ reflection = create_belongs_to_reflection(association_id, options)
650
+
651
+ ivar = "@#{reflection.name}"
652
+
653
+ if reflection.options[:polymorphic]
654
+ association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
655
+
656
+ method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym
657
+ define_method(method_name) do
658
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
659
+
660
+ if association && association.target
661
+ if association.new_record?
662
+ association.save(true)
663
+ end
664
+
665
+ if association.updated?
666
+ self["#{reflection.primary_key_name}"] = association.id
667
+ self["#{reflection.options[:foreign_type]}"] = association.class.name.to_s
668
+ end
669
+ end
670
+ end
671
+ before_save method_name
672
+ else
673
+ association_accessor_methods(reflection, BelongsToAssociation)
674
+ association_constructor_method(:build, reflection, BelongsToAssociation)
675
+ association_constructor_method(:create, reflection, BelongsToAssociation)
676
+
677
+ method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym
678
+ define_method(method_name) do
679
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
680
+
681
+ if !association.nil?
682
+ if association.new_record?
683
+ association.save(true)
684
+ end
685
+
686
+ if association.updated?
687
+ self["#{reflection.primary_key_name}"] = association.id
688
+ end
689
+ end
690
+ end
691
+ before_save method_name
692
+ end
693
+
694
+ # Create the callbacks to update counter cache
695
+ if options[:counter_cache]
696
+ cache_property = options[:counter_cache] == true ?
697
+ "#{self.to_s.demodulize.underscore.pluralize}_count" :
698
+ options[:counter_cache]
699
+
700
+ method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
701
+ define_method(method_name) do
702
+ association = send("#{reflection.name}")
703
+ association.class.increment_counter("#{cache_property}", send("#{reflection.primary_key_name}")) unless association.nil?
704
+ end
705
+ after_create method_name
706
+
707
+ method_name = "belongs_to_counter_cache_before_destroy_for_#{reflection.name}".to_sym
708
+ define_method(method_name) do
709
+ association = send("#{reflection.name}")
710
+ association.class.decrement_counter("#{cache_property}", send("#{reflection.primary_key_name}")) unless association.nil?
711
+ end
712
+ before_destroy method_name
713
+
714
+ module_eval(
715
+ "#{reflection.class_name}.send(:attr_readonly,\"#{cache_property}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)"
716
+ )
717
+ end
718
+
719
+ add_single_associated_validation_callbacks(reflection.name) if options[:validate] == true
720
+
721
+ configure_dependency_for_belongs_to(reflection)
722
+ end
723
+
724
+
725
+ # def has_and_belongs_to_many(association_id, options = {}, &extension)
726
+ # reflection = create_has_and_belongs_to_many_reflection(association_id, options, &extension)
727
+ #
728
+ # add_multiple_associated_validation_callbacks(reflection.name) unless options[:validate] == false
729
+ # add_multiple_associated_save_callbacks(reflection.name)
730
+ # collection_accessor_methods(reflection, HasAndBelongsToManyAssociation)
731
+ #
732
+ # # Don't use a before_destroy callback since users' before_destroy
733
+ # # callbacks will be executed after the association is wiped out.
734
+ # old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
735
+ # class_eval <<-end_eval unless method_defined?(old_method)
736
+ # alias_method :#{old_method}, :destroy_without_callbacks
737
+ # def destroy_without_callbacks
738
+ # #{reflection.name}.clear
739
+ # #{old_method}
740
+ # end
741
+ # end_eval
742
+ #
743
+ # add_association_callbacks(reflection.name, options)
744
+ # end
745
+
746
+ private
747
+ def association_accessor_methods(reflection, association_proxy_class)
748
+ ivar = "@#{reflection.name}"
749
+
750
+ define_method(reflection.name) do |*params|
751
+ force_reload = params.first unless params.empty?
752
+
753
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
754
+
755
+ if association.nil? || force_reload
756
+ association = association_proxy_class.new(self, reflection)
757
+ retval = association.reload
758
+ if retval.nil? and association_proxy_class == BelongsToAssociation
759
+ instance_variable_set(ivar, nil)
760
+ return nil
761
+ end
762
+ instance_variable_set(ivar, association)
763
+ end
764
+
765
+ association.target.nil? ? nil : association
766
+ end
767
+
768
+ define_method("#{reflection.name}=") do |new_value|
769
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
770
+
771
+ if association.nil? || association.target != new_value
772
+ association = association_proxy_class.new(self, reflection)
773
+ end
774
+
775
+ # if association_proxy_class == HasOneThroughAssociation
776
+ # association.create_through_record(new_value)
777
+ # self.send(reflection.name, new_value)
778
+ # else
779
+ association.replace(new_value)
780
+ instance_variable_set(ivar, new_value.nil? ? nil : association)
781
+ # end
782
+ end
783
+
784
+ define_method("set_#{reflection.name}_target") do |target|
785
+ return if target.nil? and association_proxy_class == BelongsToAssociation
786
+ association = association_proxy_class.new(self, reflection)
787
+ association.target = target
788
+ instance_variable_set(ivar, association)
789
+ end
790
+ end
791
+
792
+ def collection_reader_method(reflection, association_proxy_class)
793
+ define_method(reflection.name) do |*params|
794
+ ivar = "@#{reflection.name}"
795
+
796
+ force_reload = params.first unless params.empty?
797
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
798
+
799
+ unless association.respond_to?(:loaded?)
800
+ association = association_proxy_class.new(self, reflection)
801
+ instance_variable_set(ivar, association)
802
+ end
803
+
804
+ association.reload if force_reload
805
+
806
+ association
807
+ end
808
+
809
+ define_method("#{reflection.name.to_s.singularize}_ids") do
810
+ send(reflection.name).map(&:id)
811
+ end
812
+ end
813
+
814
+ def collection_accessor_methods(reflection, association_proxy_class, writer = true)
815
+ collection_reader_method(reflection, association_proxy_class)
816
+
817
+ if writer
818
+ define_method("#{reflection.name}=") do |new_value|
819
+ # Loads proxy class instance (defined in collection_reader_method) if not already loaded
820
+ association = send(reflection.name)
821
+ association.replace(new_value)
822
+ association
823
+ end
824
+
825
+ define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
826
+ ids = (new_value || []).reject { |nid| nid.blank? }
827
+ send("#{reflection.name}=", reflection.class_name.constantize.find(ids))
828
+ end
829
+ end
830
+ end
831
+
832
+ def add_single_associated_validation_callbacks(association_name)
833
+ method_name = "validate_associated_records_for_#{association_name}".to_sym
834
+ define_method(method_name) do
835
+ association = instance_variable_get("@#{association_name}")
836
+ if !association.nil?
837
+ errors.add "#{association_name}" unless association.target.nil? || association.valid?
838
+ end
839
+ end
840
+
841
+ validate method_name
842
+ end
843
+
844
+ def add_multiple_associated_validation_callbacks(association_name)
845
+ method_name = "validate_associated_records_for_#{association_name}".to_sym
846
+ ivar = "@#{association_name}"
847
+
848
+ define_method(method_name) do
849
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
850
+
851
+ if association.respond_to?(:loaded?)
852
+ if new_record?
853
+ association
854
+ elsif association.loaded?
855
+ association.select { |record| record.new_record? }
856
+ else
857
+ association.target.select { |record| record.new_record? }
858
+ end.each do |record|
859
+ errors.add "#{association_name}" unless record.valid?
860
+ end
861
+ end
862
+ end
863
+
864
+ validate method_name
865
+ end
866
+
867
+ def add_multiple_associated_save_callbacks(association_name)
868
+ ivar = "@#{association_name}"
869
+
870
+ method_name = "before_save_associated_records_for_#{association_name}".to_sym
871
+ define_method(method_name) do
872
+ @new_record_before_save = new_record?
873
+ true
874
+ end
875
+ before_save method_name
876
+
877
+ method_name = "after_create_or_update_associated_records_for_#{association_name}".to_sym
878
+ define_method(method_name) do
879
+ association = instance_variable_get("#{ivar}") if instance_variable_defined?("#{ivar}")
880
+
881
+ records_to_save = if @new_record_before_save
882
+ association
883
+ elsif association.respond_to?(:loaded?) && association.loaded?
884
+ association.select { |record| record.new_record? }
885
+ elsif association.respond_to?(:loaded?) && !association.loaded?
886
+ association.target.select { |record| record.new_record? }
887
+ else
888
+ []
889
+ end
890
+ records_to_save.each { |record| association.send(:insert_record, record) } unless records_to_save.blank?
891
+
892
+ # reconstruct the conditions now that we know the owner's id
893
+ association.send(:construct_conditions) if association.respond_to?(:construct_conditions)
894
+ end
895
+
896
+ # Doesn't use after_save as that would save associations added in after_create/after_update twice
897
+ after_create method_name
898
+ after_update method_name
899
+ end
900
+
901
+ def association_constructor_method(constructor, reflection, association_proxy_class)
902
+ define_method("#{constructor}_#{reflection.name}") do |*params|
903
+ ivar = "@#{reflection.name}"
904
+
905
+ attributees = params.first unless params.empty?
906
+ replace_existing = params[1].nil? ? true : params[1]
907
+ association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
908
+
909
+ if association.nil?
910
+ association = association_proxy_class.new(self, reflection)
911
+ instance_variable_set(ivar, association)
912
+ end
913
+
914
+ if association_proxy_class == HasOneAssociation
915
+ association.send(constructor, attributees, replace_existing)
916
+ else
917
+ association.send(constructor, attributees)
918
+ end
919
+ end
920
+ end
921
+
922
+ # See HasManyAssociation#delete_records. Dependent associations
923
+ # delete children, otherwise foreign key is set to NULL.
924
+ def configure_dependency_for_has_many(reflection)
925
+ if reflection.options.include?(:dependent)
926
+ case reflection.options[:dependent]
927
+ when :destroy
928
+ method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
929
+ define_method(method_name) do
930
+ send("#{reflection.name}").each { |o| o.destroy }
931
+ end
932
+ before_destroy method_name
933
+ when :delete_all
934
+ method_name = "has_many_dependent_delete_for_#{reflection.name}".to_sym
935
+ define_method(method_name) do
936
+ send("#{reflection.name}").each { |o| o.delete }
937
+ end
938
+ before_destroy method_name
939
+ when :nullify
940
+ method_name = "has_many_dependent_nullify_for_#{reflection.name}".to_sym
941
+ define_method(method_name) do
942
+ send("#{reflection.name}").each { |o| o.update_attribute({reflection.primary_key_name.to_sym => nil}, true) }
943
+ self.class.database.commit
944
+ end
945
+ before_destroy method_name
946
+ else
947
+ raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})"
948
+ end
949
+ end
950
+ end
951
+
952
+ def configure_dependency_for_has_one(reflection)
953
+ if reflection.options.include?(:dependent)
954
+ case reflection.options[:dependent]
955
+ when :destroy
956
+ method_name = "has_one_dependent_destroy_for_#{reflection.name}".to_sym
957
+ define_method(method_name) do
958
+ association = send("#{reflection.name}")
959
+ association.destroy unless association.nil?
960
+ end
961
+ before_destroy method_name
962
+ when :delete
963
+ method_name = "has_one_dependent_delete_for_#{reflection.name}".to_sym
964
+ define_method(method_name) do
965
+ association = send("#{reflection.name}")
966
+ association.class.delete(association.id) unless association.nil?
967
+ end
968
+ before_destroy method_name
969
+ when :nullify
970
+ method_name = "has_one_dependent_nullify_for_#{reflection.name}".to_sym
971
+ define_method(method_name) do
972
+ association = send("#{reflection.name}")
973
+ association.update_attribute("#{reflection.primary_key_name}", nil) unless association.nil?
974
+ end
975
+ before_destroy method_name
976
+ else
977
+ raise ArgumentError, "The :dependent option expects either :destroy, :delete or :nullify (#{reflection.options[:dependent].inspect})"
978
+ end
979
+ end
980
+ end
981
+
982
+ def configure_dependency_for_belongs_to(reflection)
983
+ if reflection.options.include?(:dependent)
984
+ case reflection.options[:dependent]
985
+ when :destroy
986
+ method_name = "belongs_to_dependent_destroy_for_#{reflection.name}".to_sym
987
+ define_method(method_name) do
988
+ association = send("#{reflection.name}")
989
+ association.destroy unless association.nil?
990
+ end
991
+ before_destroy method_name
992
+ when :delete
993
+ method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym
994
+ define_method(method_name) do
995
+ association = send("#{reflection.name}")
996
+ association.class.delete(association.id) unless association.nil?
997
+ end
998
+ before_destroy method_name
999
+ else
1000
+ raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})"
1001
+ end
1002
+ end
1003
+ end
1004
+
1005
+ def create_has_many_reflection(association_id, options, &extension)
1006
+ options.assert_valid_keys(
1007
+ :class_name, :foreign_key, :dependent,
1008
+ :conditions, :include, :order, :limit, :count, :offset, :skip,
1009
+ :as, :through, :source, :source_type,
1010
+ :uniq,
1011
+ :before_add, :after_add, :before_remove, :after_remove,
1012
+ :extend, :readonly,
1013
+ :validate,
1014
+ :startkey, :endkey, :keys, :view_type, :descending, :startkey_docid, :endkey_docid
1015
+ )
1016
+
1017
+ options[:extend] = create_extension_modules(association_id, extension, options[:extend])
1018
+
1019
+ create_reflection(:has_many, association_id, options, self)
1020
+ end
1021
+
1022
+ def create_has_one_reflection(association_id, options)
1023
+ options.assert_valid_keys(
1024
+ :class_name, :foreign_key, :remote, :conditions, :order, :include,
1025
+ :dependent, :counter_cache, :extend, :as, :readonly, :validate
1026
+ )
1027
+
1028
+ create_reflection(:has_one, association_id, options, self)
1029
+ end
1030
+
1031
+ #def create_has_one_through_reflection(association_id, options)
1032
+ # options.assert_valid_keys(
1033
+ # :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :through, :source, :source_type, :validate
1034
+ # )
1035
+ # create_reflection(:has_one, association_id, options, self)
1036
+ #end
1037
+
1038
+ def create_belongs_to_reflection(association_id, options)
1039
+ options.assert_valid_keys(
1040
+ :class_name, :foreign_key, :foreign_type, :remote, :select, :conditions,
1041
+ :include, :dependent, :counter_cache, :extend, :polymorphic, :readonly, :validate
1042
+ )
1043
+
1044
+ reflection = create_reflection(:belongs_to, association_id, options, self)
1045
+
1046
+ if options[:polymorphic]
1047
+ reflection.options[:foreign_type] ||= reflection.class_name.underscore + "_type"
1048
+ end
1049
+
1050
+ reflection
1051
+ end
1052
+
1053
+ def create_has_and_belongs_to_many_reflection(association_id, options, &extension)
1054
+ options.assert_valid_keys(
1055
+ :class_name, :foreign_key, :association_foreign_key,
1056
+ :conditions, :include, :order, :group, :offset, :skip,
1057
+ :uniq,
1058
+ :before_add, :after_add, :before_remove, :after_remove,
1059
+ :extend, :readonly,
1060
+ :validate,
1061
+ :startkey, :endkey, :keys, :view_type, :descending, :startkey_docid, :endkey_docid
1062
+ )
1063
+
1064
+ options[:extend] = create_extension_modules(association_id, extension, options[:extend])
1065
+
1066
+ reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
1067
+ # TODO rename join_table when get here
1068
+ reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
1069
+
1070
+ reflection
1071
+ end
1072
+
1073
+ def reflect_on_included_associations(associations)
1074
+ [ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) }
1075
+ end
1076
+
1077
+ def guard_against_unlimitable_reflections(reflections, options)
1078
+ if (options[:offset] || options[:limit] || options[:count]) && !using_limitable_reflections?(reflections)
1079
+ raise(
1080
+ ConfigurationError,
1081
+ "You can not use offset and limit together with has_many or has_and_belongs_to_many associations"
1082
+ )
1083
+ end
1084
+ end
1085
+
1086
+ def using_limitable_reflections?(reflections)
1087
+ reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
1088
+ end
1089
+
1090
+ def add_association_callbacks(association_name, options)
1091
+ callbacks = %w(before_add after_add before_remove after_remove)
1092
+ callbacks.each do |callback_name|
1093
+ full_callback_name = "#{callback_name}_for_#{association_name}"
1094
+ defined_callbacks = options[callback_name.to_sym]
1095
+ if options.has_key?(callback_name.to_sym)
1096
+ class_inheritable_reader full_callback_name.to_sym
1097
+ write_inheritable_attribute(full_callback_name.to_sym, [defined_callbacks].flatten)
1098
+ else
1099
+ write_inheritable_attribute(full_callback_name.to_sym, [])
1100
+ end
1101
+ end
1102
+ end
1103
+
1104
+ def create_extension_modules(association_id, block_extension, extensions)
1105
+ if block_extension
1106
+ extension_module_name = "#{self.to_s.demodulize}#{association_id.to_s.camelize}AssociationExtension"
1107
+
1108
+ silence_warnings do
1109
+ self.parent.const_set(extension_module_name, Module.new(&block_extension))
1110
+ end
1111
+ Array(extensions).push("#{self.parent}::#{extension_module_name}".constantize)
1112
+ else
1113
+ Array(extensions)
1114
+ end
1115
+ end
1116
+ end
1117
+ end
1118
+ end