duck_record 0.0.3 → 0.0.5

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,894 @@
1
+ module DuckRecord
2
+ module Associations
3
+ # Association proxies in Active Record are middlemen between the object that
4
+ # holds the association, known as the <tt>@owner</tt>, and the actual associated
5
+ # object, known as the <tt>@target</tt>. The kind of association any proxy is
6
+ # about is available in <tt>@reflection</tt>. That's an instance of the class
7
+ # ActiveRecord::Reflection::AssociationReflection.
8
+ #
9
+ # For example, given
10
+ #
11
+ # class Blog < ActiveRecord::Base
12
+ # has_many :posts
13
+ # end
14
+ #
15
+ # blog = Blog.first
16
+ #
17
+ # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
18
+ # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
19
+ # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
20
+ #
21
+ # This class delegates unknown methods to <tt>@target</tt> via
22
+ # <tt>method_missing</tt>.
23
+ #
24
+ # The <tt>@target</tt> object is not \loaded until needed. For example,
25
+ #
26
+ # blog.posts.count
27
+ #
28
+ # is computed directly through SQL and does not trigger by itself the
29
+ # instantiation of the actual post records.
30
+ class CollectionProxy
31
+ include Enumerable
32
+
33
+ delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
34
+ :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
35
+ :to_sentence, :to_formatted_s,
36
+ :shuffle, :split, :index, to: :records
37
+
38
+ attr_reader :klass
39
+ alias :model :klass
40
+
41
+ def initialize(klass, association) #:nodoc:
42
+ @klass = klass
43
+ @association = association
44
+ end
45
+
46
+ def target
47
+ @association.target
48
+ end
49
+
50
+ ##
51
+ # :method: first
52
+ #
53
+ # :call-seq:
54
+ # first(limit = nil)
55
+ #
56
+ # Returns the first record, or the first +n+ records, from the collection.
57
+ # If the collection is empty, the first form returns +nil+, and the second
58
+ # form returns an empty array.
59
+ #
60
+ # class Person < ActiveRecord::Base
61
+ # has_many :pets
62
+ # end
63
+ #
64
+ # person.pets
65
+ # # => [
66
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
67
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
68
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
69
+ # # ]
70
+ #
71
+ # person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
72
+ #
73
+ # person.pets.first(2)
74
+ # # => [
75
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
76
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
77
+ # # ]
78
+ #
79
+ # another_person_without.pets # => []
80
+ # another_person_without.pets.first # => nil
81
+ # another_person_without.pets.first(3) # => []
82
+
83
+ ##
84
+ # :method: second
85
+ #
86
+ # :call-seq:
87
+ # second()
88
+ #
89
+ # Same as #first except returns only the second record.
90
+
91
+ ##
92
+ # :method: third
93
+ #
94
+ # :call-seq:
95
+ # third()
96
+ #
97
+ # Same as #first except returns only the third record.
98
+
99
+ ##
100
+ # :method: fourth
101
+ #
102
+ # :call-seq:
103
+ # fourth()
104
+ #
105
+ # Same as #first except returns only the fourth record.
106
+
107
+ ##
108
+ # :method: fifth
109
+ #
110
+ # :call-seq:
111
+ # fifth()
112
+ #
113
+ # Same as #first except returns only the fifth record.
114
+
115
+ ##
116
+ # :method: forty_two
117
+ #
118
+ # :call-seq:
119
+ # forty_two()
120
+ #
121
+ # Same as #first except returns only the forty second record.
122
+ # Also known as accessing "the reddit".
123
+
124
+ ##
125
+ # :method: third_to_last
126
+ #
127
+ # :call-seq:
128
+ # third_to_last()
129
+ #
130
+ # Same as #first except returns only the third-to-last record.
131
+
132
+ ##
133
+ # :method: second_to_last
134
+ #
135
+ # :call-seq:
136
+ # second_to_last()
137
+ #
138
+ # Same as #first except returns only the second-to-last record.
139
+
140
+ # Returns a new object of the collection type that has been instantiated
141
+ # with +attributes+ and linked to this object, but have not yet been saved.
142
+ # You can pass an array of attributes hashes, this will return an array
143
+ # with the new objects.
144
+ #
145
+ # class Person
146
+ # has_many :pets
147
+ # end
148
+ #
149
+ # person.pets.build
150
+ # # => #<Pet id: nil, name: nil, person_id: 1>
151
+ #
152
+ # person.pets.build(name: 'Fancy-Fancy')
153
+ # # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
154
+ #
155
+ # person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
156
+ # # => [
157
+ # # #<Pet id: nil, name: "Spook", person_id: 1>,
158
+ # # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
159
+ # # #<Pet id: nil, name: "Brain", person_id: 1>
160
+ # # ]
161
+ #
162
+ # person.pets.size # => 5 # size of the collection
163
+ # person.pets.count # => 0 # count from database
164
+ def build(attributes = {}, &block)
165
+ proxy_association.build(attributes, &block)
166
+ end
167
+ alias_method :new, :build
168
+
169
+ # Add one or more records to the collection by setting their foreign keys
170
+ # to the association's primary key. Since #<< flattens its argument list and
171
+ # inserts each record, +push+ and #concat behave identically. Returns +self+
172
+ # so method calls may be chained.
173
+ #
174
+ # class Person < ActiveRecord::Base
175
+ # has_many :pets
176
+ # end
177
+ #
178
+ # person.pets.size # => 0
179
+ # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
180
+ # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
181
+ # person.pets.size # => 3
182
+ #
183
+ # person.id # => 1
184
+ # person.pets
185
+ # # => [
186
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
187
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
188
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
189
+ # # ]
190
+ #
191
+ # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
192
+ # person.pets.size # => 5
193
+ def concat(*records)
194
+ proxy_association.concat(*records)
195
+ end
196
+
197
+ # Replaces this collection with +other_array+. This will perform a diff
198
+ # and delete/add only records that have changed.
199
+ #
200
+ # class Person < ActiveRecord::Base
201
+ # has_many :pets
202
+ # end
203
+ #
204
+ # person.pets
205
+ # # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
206
+ #
207
+ # other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
208
+ #
209
+ # person.pets.replace(other_pets)
210
+ #
211
+ # person.pets
212
+ # # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
213
+ #
214
+ # If the supplied array has an incorrect association type, it raises
215
+ # an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
216
+ #
217
+ # person.pets.replace(["doo", "ggie", "gaga"])
218
+ # # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
219
+ def replace(other_array)
220
+ proxy_association.replace(other_array)
221
+ end
222
+
223
+ # Deletes all the records from the collection according to the strategy
224
+ # specified by the +:dependent+ option. If no +:dependent+ option is given,
225
+ # then it will follow the default strategy.
226
+ #
227
+ # For <tt>has_many :through</tt> associations, the default deletion strategy is
228
+ # +:delete_all+.
229
+ #
230
+ # For +has_many+ associations, the default deletion strategy is +:nullify+.
231
+ # This sets the foreign keys to +NULL+.
232
+ #
233
+ # class Person < ActiveRecord::Base
234
+ # has_many :pets # dependent: :nullify option by default
235
+ # end
236
+ #
237
+ # person.pets.size # => 3
238
+ # person.pets
239
+ # # => [
240
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
241
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
242
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
243
+ # # ]
244
+ #
245
+ # person.pets.delete_all
246
+ # # => [
247
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
248
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
249
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
250
+ # # ]
251
+ #
252
+ # person.pets.size # => 0
253
+ # person.pets # => []
254
+ #
255
+ # Pet.find(1, 2, 3)
256
+ # # => [
257
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
258
+ # # #<Pet id: 2, name: "Spook", person_id: nil>,
259
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
260
+ # # ]
261
+ #
262
+ # Both +has_many+ and <tt>has_many :through</tt> dependencies default to the
263
+ # +:delete_all+ strategy if the +:dependent+ option is set to +:destroy+.
264
+ # Records are not instantiated and callbacks will not be fired.
265
+ #
266
+ # class Person < ActiveRecord::Base
267
+ # has_many :pets, dependent: :destroy
268
+ # end
269
+ #
270
+ # person.pets.size # => 3
271
+ # person.pets
272
+ # # => [
273
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
274
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
275
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
276
+ # # ]
277
+ #
278
+ # person.pets.delete_all
279
+ #
280
+ # Pet.find(1, 2, 3)
281
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
282
+ #
283
+ # If it is set to <tt>:delete_all</tt>, all the objects are deleted
284
+ # *without* calling their +destroy+ method.
285
+ #
286
+ # class Person < ActiveRecord::Base
287
+ # has_many :pets, dependent: :delete_all
288
+ # end
289
+ #
290
+ # person.pets.size # => 3
291
+ # person.pets
292
+ # # => [
293
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
294
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
295
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
296
+ # # ]
297
+ #
298
+ # person.pets.delete_all
299
+ #
300
+ # Pet.find(1, 2, 3)
301
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
302
+ def delete_all
303
+ proxy_association.delete_all
304
+ end
305
+
306
+ # Deletes the records of the collection directly from the database
307
+ # ignoring the +:dependent+ option. Records are instantiated and it
308
+ # invokes +before_remove+, +after_remove+ , +before_destroy+ and
309
+ # +after_destroy+ callbacks.
310
+ #
311
+ # class Person < ActiveRecord::Base
312
+ # has_many :pets
313
+ # end
314
+ #
315
+ # person.pets.size # => 3
316
+ # person.pets
317
+ # # => [
318
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
319
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
320
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
321
+ # # ]
322
+ #
323
+ # person.pets.destroy_all
324
+ #
325
+ # person.pets.size # => 0
326
+ # person.pets # => []
327
+ #
328
+ # Pet.find(1) # => Couldn't find Pet with id=1
329
+ def destroy_all
330
+ proxy_association.destroy_all
331
+ end
332
+
333
+ # Deletes the +records+ supplied from the collection according to the strategy
334
+ # specified by the +:dependent+ option. If no +:dependent+ option is given,
335
+ # then it will follow the default strategy. Returns an array with the
336
+ # deleted records.
337
+ #
338
+ # For <tt>has_many :through</tt> associations, the default deletion strategy is
339
+ # +:delete_all+.
340
+ #
341
+ # For +has_many+ associations, the default deletion strategy is +:nullify+.
342
+ # This sets the foreign keys to +NULL+.
343
+ #
344
+ # class Person < ActiveRecord::Base
345
+ # has_many :pets # dependent: :nullify option by default
346
+ # end
347
+ #
348
+ # person.pets.size # => 3
349
+ # person.pets
350
+ # # => [
351
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
352
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
353
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
354
+ # # ]
355
+ #
356
+ # person.pets.delete(Pet.find(1))
357
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
358
+ #
359
+ # person.pets.size # => 2
360
+ # person.pets
361
+ # # => [
362
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
363
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
364
+ # # ]
365
+ #
366
+ # Pet.find(1)
367
+ # # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
368
+ #
369
+ # If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
370
+ # their +destroy+ method. See +destroy+ for more information.
371
+ #
372
+ # class Person < ActiveRecord::Base
373
+ # has_many :pets, dependent: :destroy
374
+ # end
375
+ #
376
+ # person.pets.size # => 3
377
+ # person.pets
378
+ # # => [
379
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
380
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
381
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
382
+ # # ]
383
+ #
384
+ # person.pets.delete(Pet.find(1), Pet.find(3))
385
+ # # => [
386
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
387
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
388
+ # # ]
389
+ #
390
+ # person.pets.size # => 1
391
+ # person.pets
392
+ # # => [#<Pet id: 2, name: "Spook", person_id: 1>]
393
+ #
394
+ # Pet.find(1, 3)
395
+ # # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 3)
396
+ #
397
+ # If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
398
+ # *without* calling their +destroy+ method.
399
+ #
400
+ # class Person < ActiveRecord::Base
401
+ # has_many :pets, dependent: :delete_all
402
+ # end
403
+ #
404
+ # person.pets.size # => 3
405
+ # person.pets
406
+ # # => [
407
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
408
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
409
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
410
+ # # ]
411
+ #
412
+ # person.pets.delete(Pet.find(1))
413
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
414
+ #
415
+ # person.pets.size # => 2
416
+ # person.pets
417
+ # # => [
418
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
419
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
420
+ # # ]
421
+ #
422
+ # Pet.find(1)
423
+ # # => ActiveRecord::RecordNotFound: Couldn't find Pet with 'id'=1
424
+ #
425
+ # You can pass +Integer+ or +String+ values, it finds the records
426
+ # responding to the +id+ and executes delete on them.
427
+ #
428
+ # class Person < ActiveRecord::Base
429
+ # has_many :pets
430
+ # end
431
+ #
432
+ # person.pets.size # => 3
433
+ # person.pets
434
+ # # => [
435
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
436
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
437
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
438
+ # # ]
439
+ #
440
+ # person.pets.delete("1")
441
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
442
+ #
443
+ # person.pets.delete(2, 3)
444
+ # # => [
445
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
446
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
447
+ # # ]
448
+ def delete(*records)
449
+ proxy_association.delete(*records)
450
+ end
451
+
452
+ # Destroys the +records+ supplied and removes them from the collection.
453
+ # This method will _always_ remove record from the database ignoring
454
+ # the +:dependent+ option. Returns an array with the removed records.
455
+ #
456
+ # class Person < ActiveRecord::Base
457
+ # has_many :pets
458
+ # end
459
+ #
460
+ # person.pets.size # => 3
461
+ # person.pets
462
+ # # => [
463
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
464
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
465
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
466
+ # # ]
467
+ #
468
+ # person.pets.destroy(Pet.find(1))
469
+ # # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
470
+ #
471
+ # person.pets.size # => 2
472
+ # person.pets
473
+ # # => [
474
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
475
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
476
+ # # ]
477
+ #
478
+ # person.pets.destroy(Pet.find(2), Pet.find(3))
479
+ # # => [
480
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
481
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
482
+ # # ]
483
+ #
484
+ # person.pets.size # => 0
485
+ # person.pets # => []
486
+ #
487
+ # Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (1, 2, 3)
488
+ #
489
+ # You can pass +Integer+ or +String+ values, it finds the records
490
+ # responding to the +id+ and then deletes them from the database.
491
+ #
492
+ # person.pets.size # => 3
493
+ # person.pets
494
+ # # => [
495
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
496
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
497
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
498
+ # # ]
499
+ #
500
+ # person.pets.destroy("4")
501
+ # # => #<Pet id: 4, name: "Benny", person_id: 1>
502
+ #
503
+ # person.pets.size # => 2
504
+ # person.pets
505
+ # # => [
506
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
507
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
508
+ # # ]
509
+ #
510
+ # person.pets.destroy(5, 6)
511
+ # # => [
512
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
513
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
514
+ # # ]
515
+ #
516
+ # person.pets.size # => 0
517
+ # person.pets # => []
518
+ #
519
+ # Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with 'id': (4, 5, 6)
520
+ def destroy(*records)
521
+ proxy_association.destroy(*records)
522
+ end
523
+
524
+ ##
525
+ # :method: distinct
526
+ #
527
+ # :call-seq:
528
+ # distinct(value = true)
529
+ #
530
+ # Specifies whether the records should be unique or not.
531
+ #
532
+ # class Person < ActiveRecord::Base
533
+ # has_many :pets
534
+ # end
535
+ #
536
+ # person.pets.select(:name)
537
+ # # => [
538
+ # # #<Pet name: "Fancy-Fancy">,
539
+ # # #<Pet name: "Fancy-Fancy">
540
+ # # ]
541
+ #
542
+ # person.pets.select(:name).distinct
543
+ # # => [#<Pet name: "Fancy-Fancy">]
544
+ #
545
+ # person.pets.select(:name).distinct.distinct(false)
546
+ # # => [
547
+ # # #<Pet name: "Fancy-Fancy">,
548
+ # # #<Pet name: "Fancy-Fancy">
549
+ # # ]
550
+
551
+ #--
552
+ def uniq
553
+ proxy_association.uniq
554
+ end
555
+
556
+ ##
557
+ # :method: count
558
+ #
559
+ # :call-seq:
560
+ # count(column_name = nil, &block)
561
+ #
562
+ # Count all records.
563
+ #
564
+ # class Person < ActiveRecord::Base
565
+ # has_many :pets
566
+ # end
567
+ #
568
+ # # This will perform the count using SQL.
569
+ # person.pets.count # => 3
570
+ # person.pets
571
+ # # => [
572
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
573
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
574
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
575
+ # # ]
576
+ #
577
+ # Passing a block will select all of a person's pets in SQL and then
578
+ # perform the count using Ruby.
579
+ #
580
+ # person.pets.count { |pet| pet.name.include?('-') } # => 2
581
+
582
+ # Returns the size of the collection. If the collection hasn't been loaded,
583
+ # it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
584
+ #
585
+ # If the collection has been already loaded +size+ and +length+ are
586
+ # equivalent. If not and you are going to need the records anyway
587
+ # +length+ will take one less query. Otherwise +size+ is more efficient.
588
+ #
589
+ # class Person < ActiveRecord::Base
590
+ # has_many :pets
591
+ # end
592
+ #
593
+ # person.pets.size # => 3
594
+ # # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
595
+ #
596
+ # person.pets # This will execute a SELECT * FROM query
597
+ # # => [
598
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
599
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
600
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
601
+ # # ]
602
+ #
603
+ # person.pets.size # => 3
604
+ # # Because the collection is already loaded, this will behave like
605
+ # # collection.size and no SQL count query is executed.
606
+ def size
607
+ proxy_association.size
608
+ end
609
+
610
+ ##
611
+ # :method: length
612
+ #
613
+ # :call-seq:
614
+ # length()
615
+ #
616
+ # Returns the size of the collection calling +size+ on the target.
617
+ # If the collection has been already loaded, +length+ and +size+ are
618
+ # equivalent. If not and you are going to need the records anyway this
619
+ # method will take one less query. Otherwise +size+ is more efficient.
620
+ #
621
+ # class Person < ActiveRecord::Base
622
+ # has_many :pets
623
+ # end
624
+ #
625
+ # person.pets.length # => 3
626
+ # # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
627
+ #
628
+ # # Because the collection is loaded, you can
629
+ # # call the collection with no additional queries:
630
+ # person.pets
631
+ # # => [
632
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
633
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
634
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
635
+ # # ]
636
+
637
+ # Returns +true+ if the collection is empty. If the collection has been
638
+ # loaded it is equivalent
639
+ # to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
640
+ # it is equivalent to <tt>!collection.exists?</tt>. If the collection has
641
+ # not already been loaded and you are going to fetch the records anyway it
642
+ # is better to check <tt>collection.length.zero?</tt>.
643
+ #
644
+ # class Person < ActiveRecord::Base
645
+ # has_many :pets
646
+ # end
647
+ #
648
+ # person.pets.count # => 1
649
+ # person.pets.empty? # => false
650
+ #
651
+ # person.pets.delete_all
652
+ #
653
+ # person.pets.count # => 0
654
+ # person.pets.empty? # => true
655
+ def empty?
656
+ proxy_association.empty?
657
+ end
658
+
659
+ ##
660
+ # :method: any?
661
+ #
662
+ # :call-seq:
663
+ # any?()
664
+ #
665
+ # Returns +true+ if the collection is not empty.
666
+ #
667
+ # class Person < ActiveRecord::Base
668
+ # has_many :pets
669
+ # end
670
+ #
671
+ # person.pets.count # => 0
672
+ # person.pets.any? # => false
673
+ #
674
+ # person.pets << Pet.new(name: 'Snoop')
675
+ # person.pets.count # => 1
676
+ # person.pets.any? # => true
677
+ #
678
+ # You can also pass a +block+ to define criteria. The behavior
679
+ # is the same, it returns true if the collection based on the
680
+ # criteria is not empty.
681
+ #
682
+ # person.pets
683
+ # # => [#<Pet name: "Snoop", group: "dogs">]
684
+ #
685
+ # person.pets.any? do |pet|
686
+ # pet.group == 'cats'
687
+ # end
688
+ # # => false
689
+ #
690
+ # person.pets.any? do |pet|
691
+ # pet.group == 'dogs'
692
+ # end
693
+ # # => true
694
+
695
+ ##
696
+ # :method: many?
697
+ #
698
+ # :call-seq:
699
+ # many?()
700
+ #
701
+ # Returns true if the collection has more than one record.
702
+ # Equivalent to <tt>collection.size > 1</tt>.
703
+ #
704
+ # class Person < ActiveRecord::Base
705
+ # has_many :pets
706
+ # end
707
+ #
708
+ # person.pets.count # => 1
709
+ # person.pets.many? # => false
710
+ #
711
+ # person.pets << Pet.new(name: 'Snoopy')
712
+ # person.pets.count # => 2
713
+ # person.pets.many? # => true
714
+ #
715
+ # You can also pass a +block+ to define criteria. The
716
+ # behavior is the same, it returns true if the collection
717
+ # based on the criteria has more than one record.
718
+ #
719
+ # person.pets
720
+ # # => [
721
+ # # #<Pet name: "Gorby", group: "cats">,
722
+ # # #<Pet name: "Puff", group: "cats">,
723
+ # # #<Pet name: "Snoop", group: "dogs">
724
+ # # ]
725
+ #
726
+ # person.pets.many? do |pet|
727
+ # pet.group == 'dogs'
728
+ # end
729
+ # # => false
730
+ #
731
+ # person.pets.many? do |pet|
732
+ # pet.group == 'cats'
733
+ # end
734
+ # # => true
735
+
736
+ # Returns +true+ if the given +record+ is present in the collection.
737
+ #
738
+ # class Person < ActiveRecord::Base
739
+ # has_many :pets
740
+ # end
741
+ #
742
+ # person.pets # => [#<Pet id: 20, name: "Snoop">]
743
+ #
744
+ # person.pets.include?(Pet.find(20)) # => true
745
+ # person.pets.include?(Pet.find(21)) # => false
746
+ def include?(record)
747
+ !!proxy_association.include?(record)
748
+ end
749
+
750
+ def proxy_association
751
+ @association
752
+ end
753
+
754
+ # Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
755
+ # contain the same number of elements and if each element is equal
756
+ # to the corresponding element in the +other+ array, otherwise returns
757
+ # +false+.
758
+ #
759
+ # class Person < ActiveRecord::Base
760
+ # has_many :pets
761
+ # end
762
+ #
763
+ # person.pets
764
+ # # => [
765
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
766
+ # # #<Pet id: 2, name: "Spook", person_id: 1>
767
+ # # ]
768
+ #
769
+ # other = person.pets.to_ary
770
+ #
771
+ # person.pets == other
772
+ # # => true
773
+ #
774
+ # other = [Pet.new(id: 1), Pet.new(id: 2)]
775
+ #
776
+ # person.pets == other
777
+ # # => false
778
+ def ==(other)
779
+ proxy_association.target == other
780
+ end
781
+
782
+ # Returns a new array of objects from the collection. If the collection
783
+ # hasn't been loaded, it fetches the records from the database.
784
+ #
785
+ # class Person < ActiveRecord::Base
786
+ # has_many :pets
787
+ # end
788
+ #
789
+ # person.pets
790
+ # # => [
791
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
792
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
793
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
794
+ # # ]
795
+ #
796
+ # other_pets = person.pets.to_ary
797
+ # # => [
798
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
799
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
800
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
801
+ # # ]
802
+ #
803
+ # other_pets.replace([Pet.new(name: 'BooGoo')])
804
+ #
805
+ # other_pets
806
+ # # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
807
+ #
808
+ # person.pets
809
+ # # This is not affected by replace
810
+ # # => [
811
+ # # #<Pet id: 4, name: "Benny", person_id: 1>,
812
+ # # #<Pet id: 5, name: "Brain", person_id: 1>,
813
+ # # #<Pet id: 6, name: "Boss", person_id: 1>
814
+ # # ]
815
+ def to_ary
816
+ proxy_association.target.dup
817
+ end
818
+ alias_method :to_a, :to_ary
819
+
820
+ def records # :nodoc:
821
+ proxy_association.target
822
+ end
823
+
824
+ # Adds one or more +records+ to the collection by setting their foreign keys
825
+ # to the association's primary key. Returns +self+, so several appends may be
826
+ # chained together.
827
+ #
828
+ # class Person < ActiveRecord::Base
829
+ # has_many :pets
830
+ # end
831
+ #
832
+ # person.pets.size # => 0
833
+ # person.pets << Pet.new(name: 'Fancy-Fancy')
834
+ # person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
835
+ # person.pets.size # => 3
836
+ #
837
+ # person.id # => 1
838
+ # person.pets
839
+ # # => [
840
+ # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
841
+ # # #<Pet id: 2, name: "Spook", person_id: 1>,
842
+ # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
843
+ # # ]
844
+ def <<(*records)
845
+ proxy_association.concat(records) && self
846
+ end
847
+ alias_method :push, :<<
848
+ alias_method :append, :<<
849
+
850
+ def prepend(*_args)
851
+ raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
852
+ end
853
+
854
+ # Equivalent to +delete_all+. The difference is that returns +self+, instead
855
+ # of an array with the deleted objects, so methods can be chained. See
856
+ # +delete_all+ for more information.
857
+ # Note that because +delete_all+ removes records by directly
858
+ # running an SQL query into the database, the +updated_at+ column of
859
+ # the object is not changed.
860
+ def clear
861
+ delete_all
862
+ self
863
+ end
864
+
865
+ # Unloads the association. Returns +self+.
866
+ #
867
+ # class Person < ActiveRecord::Base
868
+ # has_many :pets
869
+ # end
870
+ #
871
+ # person.pets # fetches pets from the database
872
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
873
+ #
874
+ # person.pets # uses the pets cache
875
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
876
+ #
877
+ # person.pets.reset # clears the pets cache
878
+ #
879
+ # person.pets # fetches pets from the database
880
+ # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
881
+ def reset
882
+ proxy_association.reset
883
+ self
884
+ end
885
+
886
+ def inspect
887
+ entries = records.take(11).map!(&:inspect)
888
+ entries[10] = '...' if entries.size == 11
889
+
890
+ "#<#{self.class.name} [#{entries.join(', ')}]>"
891
+ end
892
+ end
893
+ end
894
+ end