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