activerecord 3.2.22.5 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1024 -543
- data/MIT-LICENSE +1 -1
- data/README.rdoc +20 -29
- data/examples/performance.rb +1 -1
- data/lib/active_record.rb +55 -44
- data/lib/active_record/aggregations.rb +40 -34
- data/lib/active_record/associations.rb +204 -276
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +30 -35
- data/lib/active_record/associations/association_scope.rb +40 -40
- data/lib/active_record/associations/belongs_to_association.rb +15 -2
- data/lib/active_record/associations/builder/association.rb +81 -28
- data/lib/active_record/associations/builder/belongs_to.rb +35 -57
- data/lib/active_record/associations/builder/collection_association.rb +54 -40
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +23 -41
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +13 -50
- data/lib/active_record/associations/builder/singular_association.rb +13 -13
- data/lib/active_record/associations/collection_association.rb +92 -88
- data/lib/active_record/associations/collection_proxy.rb +913 -63
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -10
- data/lib/active_record/associations/has_many_association.rb +35 -9
- data/lib/active_record/associations/has_many_through_association.rb +24 -14
- data/lib/active_record/associations/has_one_association.rb +33 -13
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/join_dependency/join_association.rb +17 -22
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/join_helper.rb +1 -11
- data/lib/active_record/associations/preloader.rb +14 -17
- data/lib/active_record/associations/preloader/association.rb +29 -33
- data/lib/active_record/associations/preloader/collection_association.rb +1 -1
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +1 -1
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/through_association.rb +13 -17
- data/lib/active_record/associations/singular_association.rb +11 -11
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +133 -153
- data/lib/active_record/attribute_methods.rb +196 -93
- data/lib/active_record/attribute_methods/before_type_cast.rb +44 -5
- data/lib/active_record/attribute_methods/dirty.rb +31 -28
- data/lib/active_record/attribute_methods/primary_key.rb +38 -30
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +62 -91
- data/lib/active_record/attribute_methods/serialization.rb +97 -66
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -45
- data/lib/active_record/attribute_methods/write.rb +32 -39
- data/lib/active_record/autosave_association.rb +56 -70
- data/lib/active_record/base.rb +53 -450
- data/lib/active_record/callbacks.rb +53 -18
- data/lib/active_record/coders/yaml_column.rb +11 -9
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +353 -197
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +130 -131
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +24 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -3
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +101 -91
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +59 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +225 -96
- data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +99 -46
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +114 -36
- data/lib/active_record/connection_adapters/column.rb +46 -24
- data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +16 -32
- data/lib/active_record/connection_adapters/mysql_adapter.rb +181 -64
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
- data/lib/active_record/connection_adapters/postgresql/cast.rb +132 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +347 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +158 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +448 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +454 -885
- data/lib/active_record/connection_adapters/schema_cache.rb +48 -16
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +574 -13
- data/lib/active_record/connection_handling.rb +98 -0
- data/lib/active_record/core.rb +428 -0
- data/lib/active_record/counter_cache.rb +106 -108
- data/lib/active_record/dynamic_matchers.rb +110 -63
- data/lib/active_record/errors.rb +25 -8
- data/lib/active_record/explain.rb +8 -58
- data/lib/active_record/explain_subscriber.rb +6 -3
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +146 -148
- data/lib/active_record/inheritance.rb +77 -59
- data/lib/active_record/integration.rb +5 -5
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +38 -42
- data/lib/active_record/locking/pessimistic.rb +4 -4
- data/lib/active_record/log_subscriber.rb +19 -9
- data/lib/active_record/migration.rb +318 -153
- data/lib/active_record/migration/command_recorder.rb +90 -31
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/model_schema.rb +69 -92
- data/lib/active_record/nested_attributes.rb +113 -148
- data/lib/active_record/null_relation.rb +65 -0
- data/lib/active_record/persistence.rb +188 -97
- data/lib/active_record/query_cache.rb +18 -36
- data/lib/active_record/querying.rb +19 -15
- data/lib/active_record/railtie.rb +91 -36
- data/lib/active_record/railties/console_sandbox.rb +0 -2
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +90 -309
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +7 -3
- data/lib/active_record/reflection.rb +72 -56
- data/lib/active_record/relation.rb +241 -157
- data/lib/active_record/relation/batches.rb +25 -22
- data/lib/active_record/relation/calculations.rb +143 -121
- data/lib/active_record/relation/delegation.rb +96 -18
- data/lib/active_record/relation/finder_methods.rb +117 -183
- data/lib/active_record/relation/merger.rb +133 -0
- data/lib/active_record/relation/predicate_builder.rb +90 -42
- data/lib/active_record/relation/query_methods.rb +666 -136
- data/lib/active_record/relation/spawn_methods.rb +43 -150
- data/lib/active_record/result.rb +33 -6
- data/lib/active_record/sanitization.rb +24 -50
- data/lib/active_record/schema.rb +19 -12
- data/lib/active_record/schema_dumper.rb +31 -39
- data/lib/active_record/schema_migration.rb +36 -0
- data/lib/active_record/scoping.rb +0 -124
- data/lib/active_record/scoping/default.rb +48 -45
- data/lib/active_record/scoping/named.rb +74 -103
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +9 -15
- data/lib/active_record/store.rb +119 -15
- data/lib/active_record/tasks/database_tasks.rb +158 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +138 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
- data/lib/active_record/test_case.rb +61 -38
- data/lib/active_record/timestamp.rb +8 -9
- data/lib/active_record/transactions.rb +65 -51
- data/lib/active_record/validations.rb +17 -15
- data/lib/active_record/validations/associated.rb +20 -14
- data/lib/active_record/validations/presence.rb +65 -0
- data/lib/active_record/validations/uniqueness.rb +93 -52
- data/lib/active_record/version.rb +4 -4
- data/lib/rails/generators/active_record.rb +3 -5
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -7
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -3
- data/lib/rails/generators/active_record/model/templates/model.rb +1 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- metadata +53 -46
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -360
- data/lib/rails/generators/active_record/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,6 +1,8 @@
|
|
1
1
|
module ActiveRecord::Associations::Builder
|
2
2
|
class SingularAssociation < Association #:nodoc:
|
3
|
-
|
3
|
+
def valid_options
|
4
|
+
super + [:remote, :dependent, :counter_cache, :primary_key, :inverse_of]
|
5
|
+
end
|
4
6
|
|
5
7
|
def constructable?
|
6
8
|
true
|
@@ -11,22 +13,20 @@ module ActiveRecord::Associations::Builder
|
|
11
13
|
define_constructors if constructable?
|
12
14
|
end
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
mixin.redefine_method("build_#{name}") do |*params, &block|
|
20
|
-
association(name).build(*params, &block)
|
16
|
+
def define_constructors
|
17
|
+
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
18
|
+
def build_#{name}(*args, &block)
|
19
|
+
association(:#{name}).build(*args, &block)
|
21
20
|
end
|
22
21
|
|
23
|
-
|
24
|
-
association(name).create(*
|
22
|
+
def create_#{name}(*args, &block)
|
23
|
+
association(:#{name}).create(*args, &block)
|
25
24
|
end
|
26
25
|
|
27
|
-
|
28
|
-
association(name).create!(*
|
26
|
+
def create_#{name}!(*args, &block)
|
27
|
+
association(:#{name}).create!(*args, &block)
|
29
28
|
end
|
30
|
-
|
29
|
+
CODE
|
30
|
+
end
|
31
31
|
end
|
32
32
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'active_support/core_ext/array/wrap'
|
2
|
-
|
3
1
|
module ActiveRecord
|
4
2
|
module Associations
|
5
3
|
# = Active Record Association Collection
|
@@ -8,6 +6,15 @@ module ActiveRecord
|
|
8
6
|
# ease the implementation of association proxies that represent
|
9
7
|
# collections. See the class hierarchy in AssociationProxy.
|
10
8
|
#
|
9
|
+
# CollectionAssociation:
|
10
|
+
# HasAndBelongsToManyAssociation => has_and_belongs_to_many
|
11
|
+
# HasManyAssociation => has_many
|
12
|
+
# HasManyThroughAssociation + ThroughAssociation => has_many :through
|
13
|
+
#
|
14
|
+
# CollectionAssociation class provides common methods to the collections
|
15
|
+
# defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
|
16
|
+
# +:through association+ option.
|
17
|
+
#
|
11
18
|
# You need to be careful with assumptions regarding the target: The proxy
|
12
19
|
# does not fetch records from the database until it needs them, but new
|
13
20
|
# ones created with +build+ are added to the target. So, the target may be
|
@@ -18,12 +25,6 @@ module ActiveRecord
|
|
18
25
|
# If you need to work on all current children, new and existing records,
|
19
26
|
# +load_target+ and the +loaded+ flag are your friends.
|
20
27
|
class CollectionAssociation < Association #:nodoc:
|
21
|
-
attr_reader :proxy
|
22
|
-
|
23
|
-
def initialize(owner, reflection)
|
24
|
-
super
|
25
|
-
@proxy = CollectionProxy.new(self)
|
26
|
-
end
|
27
28
|
|
28
29
|
# Implements the reader method, e.g. foo.items for Foo.has_many :items
|
29
30
|
def reader(force_reload = false)
|
@@ -33,7 +34,7 @@ module ActiveRecord
|
|
33
34
|
reload
|
34
35
|
end
|
35
36
|
|
36
|
-
|
37
|
+
CollectionProxy.new(klass, self)
|
37
38
|
end
|
38
39
|
|
39
40
|
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
|
@@ -43,37 +44,26 @@ module ActiveRecord
|
|
43
44
|
|
44
45
|
# Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
|
45
46
|
def ids_reader
|
46
|
-
if
|
47
|
+
if loaded? || options[:finder_sql]
|
47
48
|
load_target.map do |record|
|
48
49
|
record.send(reflection.association_primary_key)
|
49
50
|
end
|
50
51
|
else
|
51
52
|
column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
|
52
|
-
|
53
|
-
|
54
|
-
including = (relation.eager_load_values + relation.includes_values).uniq
|
55
|
-
|
56
|
-
if including.any?
|
57
|
-
join_dependency = ActiveRecord::Associations::JoinDependency.new(reflection.klass, including, [])
|
58
|
-
relation = join_dependency.join_associations.inject(relation) do |r, association|
|
59
|
-
association.join_relation(r)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
relation.pluck(column)
|
53
|
+
scope.pluck(column)
|
64
54
|
end
|
65
55
|
end
|
66
56
|
|
67
57
|
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
|
68
58
|
def ids_writer(ids)
|
69
59
|
pk_column = reflection.primary_key_column
|
70
|
-
ids = Array
|
60
|
+
ids = Array(ids).reject { |id| id.blank? }
|
71
61
|
ids.map! { |i| pk_column.type_cast(i) }
|
72
62
|
replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
|
73
63
|
end
|
74
64
|
|
75
65
|
def reset
|
76
|
-
|
66
|
+
super
|
77
67
|
@target = []
|
78
68
|
end
|
79
69
|
|
@@ -81,7 +71,7 @@ module ActiveRecord
|
|
81
71
|
if block_given?
|
82
72
|
load_target.select.each { |e| yield e }
|
83
73
|
else
|
84
|
-
|
74
|
+
scope.select(select)
|
85
75
|
end
|
86
76
|
end
|
87
77
|
|
@@ -92,7 +82,7 @@ module ActiveRecord
|
|
92
82
|
if options[:finder_sql]
|
93
83
|
find_by_scan(*args)
|
94
84
|
else
|
95
|
-
|
85
|
+
scope.find(*args)
|
96
86
|
end
|
97
87
|
end
|
98
88
|
end
|
@@ -105,26 +95,27 @@ module ActiveRecord
|
|
105
95
|
first_or_last(:last, *args)
|
106
96
|
end
|
107
97
|
|
108
|
-
def build(attributes = {},
|
98
|
+
def build(attributes = {}, &block)
|
109
99
|
if attributes.is_a?(Array)
|
110
|
-
attributes.collect { |attr| build(attr,
|
100
|
+
attributes.collect { |attr| build(attr, &block) }
|
111
101
|
else
|
112
|
-
add_to_target(build_record(attributes
|
102
|
+
add_to_target(build_record(attributes)) do |record|
|
113
103
|
yield(record) if block_given?
|
114
104
|
end
|
115
105
|
end
|
116
106
|
end
|
117
107
|
|
118
|
-
def create(attributes = {},
|
119
|
-
create_record(attributes,
|
108
|
+
def create(attributes = {}, &block)
|
109
|
+
create_record(attributes, &block)
|
120
110
|
end
|
121
111
|
|
122
|
-
def create!(attributes = {},
|
123
|
-
create_record(attributes,
|
112
|
+
def create!(attributes = {}, &block)
|
113
|
+
create_record(attributes, true, &block)
|
124
114
|
end
|
125
115
|
|
126
|
-
# Add +records+ to this association. Returns +self+ so method calls may
|
127
|
-
# Since << flattens its argument list and inserts each record,
|
116
|
+
# Add +records+ to this association. Returns +self+ so method calls may
|
117
|
+
# be chained. Since << flattens its argument list and inserts each record,
|
118
|
+
# +push+ and +concat+ behave identically.
|
128
119
|
def concat(*records)
|
129
120
|
load_target if owner.new_record?
|
130
121
|
|
@@ -150,23 +141,16 @@ module ActiveRecord
|
|
150
141
|
end
|
151
142
|
end
|
152
143
|
|
153
|
-
# Remove all records from this association
|
144
|
+
# Remove all records from this association.
|
154
145
|
#
|
155
146
|
# See delete for more info.
|
156
147
|
def delete_all
|
157
|
-
delete(
|
148
|
+
delete(:all).tap do
|
158
149
|
reset
|
159
150
|
loaded!
|
160
151
|
end
|
161
152
|
end
|
162
153
|
|
163
|
-
# Called when the association is declared as :dependent => :delete_all. This is
|
164
|
-
# an optimised version which avoids loading the records into memory. Not really
|
165
|
-
# for public consumption.
|
166
|
-
def delete_all_on_destroy
|
167
|
-
scoped.delete_all
|
168
|
-
end
|
169
|
-
|
170
154
|
# Destroy all the records from this association.
|
171
155
|
#
|
172
156
|
# See destroy for more info.
|
@@ -177,21 +161,10 @@ module ActiveRecord
|
|
177
161
|
end
|
178
162
|
end
|
179
163
|
|
180
|
-
# Calculate sum using SQL, not Enumerable
|
181
|
-
def sum(*args)
|
182
|
-
if block_given?
|
183
|
-
scoped.sum(*args) { |*block_args| yield(*block_args) }
|
184
|
-
else
|
185
|
-
scoped.sum(*args)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
164
|
# Count all records using SQL. If the +:counter_sql+ or +:finder_sql+ option is set for the
|
190
165
|
# association, it will be used for the query. Otherwise, construct options and pass them with
|
191
166
|
# scope to the target class's +count+.
|
192
167
|
def count(column_name = nil, count_options = {})
|
193
|
-
return 0 if owner.new_record?
|
194
|
-
|
195
168
|
column_name, count_options = nil, column_name if column_name.is_a?(Hash)
|
196
169
|
|
197
170
|
if options[:counter_sql] || options[:finder_sql]
|
@@ -201,13 +174,13 @@ module ActiveRecord
|
|
201
174
|
|
202
175
|
reflection.klass.count_by_sql(custom_counter_sql)
|
203
176
|
else
|
204
|
-
if
|
177
|
+
if association_scope.uniq_value
|
205
178
|
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
206
179
|
column_name ||= reflection.klass.primary_key
|
207
|
-
count_options
|
180
|
+
count_options[:distinct] = true
|
208
181
|
end
|
209
182
|
|
210
|
-
value =
|
183
|
+
value = scope.count(column_name, count_options)
|
211
184
|
|
212
185
|
limit = options[:limit]
|
213
186
|
offset = options[:offset]
|
@@ -228,7 +201,18 @@ module ActiveRecord
|
|
228
201
|
# are actually removed from the database, that depends precisely on
|
229
202
|
# +delete_records+. They are in any case removed from the collection.
|
230
203
|
def delete(*records)
|
231
|
-
|
204
|
+
dependent = options[:dependent]
|
205
|
+
|
206
|
+
if records.first == :all
|
207
|
+
if loaded? || dependent == :destroy
|
208
|
+
delete_or_destroy(load_target, dependent)
|
209
|
+
else
|
210
|
+
delete_records(:all, dependent)
|
211
|
+
end
|
212
|
+
else
|
213
|
+
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
|
214
|
+
delete_or_destroy(records, dependent)
|
215
|
+
end
|
232
216
|
end
|
233
217
|
|
234
218
|
# Destroy +records+ and remove them from this association calling
|
@@ -252,11 +236,15 @@ module ActiveRecord
|
|
252
236
|
# This method is abstract in the sense that it relies on
|
253
237
|
# +count_records+, which is a method descendants have to provide.
|
254
238
|
def size
|
255
|
-
if !find_target? ||
|
256
|
-
|
257
|
-
|
239
|
+
if !find_target? || loaded?
|
240
|
+
if association_scope.uniq_value
|
241
|
+
target.uniq.size
|
242
|
+
else
|
243
|
+
target.size
|
244
|
+
end
|
245
|
+
elsif !loaded? && !association_scope.group_values.empty?
|
258
246
|
load_target.size
|
259
|
-
elsif !loaded? && !
|
247
|
+
elsif !loaded? && !association_scope.uniq_value && target.is_a?(Array)
|
260
248
|
unsaved_records = target.select { |r| r.new_record? }
|
261
249
|
unsaved_records.size + count_records
|
262
250
|
else
|
@@ -273,13 +261,24 @@ module ActiveRecord
|
|
273
261
|
load_target.size
|
274
262
|
end
|
275
263
|
|
276
|
-
#
|
277
|
-
#
|
278
|
-
#
|
264
|
+
# Returns true if the collection is empty.
|
265
|
+
#
|
266
|
+
# If the collection has been loaded or the <tt>:counter_sql</tt> option
|
267
|
+
# is provided, it is equivalent to <tt>collection.size.zero?</tt>. If the
|
268
|
+
# collection has not been loaded, it is equivalent to
|
269
|
+
# <tt>collection.exists?</tt>. If the collection has not already been
|
270
|
+
# loaded and you are going to fetch the records anyway it is better to
|
271
|
+
# check <tt>collection.length.zero?</tt>.
|
279
272
|
def empty?
|
280
|
-
|
273
|
+
if loaded? || options[:counter_sql]
|
274
|
+
size.zero?
|
275
|
+
else
|
276
|
+
@target.blank? && !scope.exists?
|
277
|
+
end
|
281
278
|
end
|
282
279
|
|
280
|
+
# Returns true if the collections is not empty.
|
281
|
+
# Equivalent to +!collection.empty?+.
|
283
282
|
def any?
|
284
283
|
if block_given?
|
285
284
|
load_target.any? { |*block_args| yield(*block_args) }
|
@@ -288,7 +287,8 @@ module ActiveRecord
|
|
288
287
|
end
|
289
288
|
end
|
290
289
|
|
291
|
-
# Returns true if the collection has more than 1 record.
|
290
|
+
# Returns true if the collection has more than 1 record.
|
291
|
+
# Equivalent to +collection.size > 1+.
|
292
292
|
def many?
|
293
293
|
if block_given?
|
294
294
|
load_target.many? { |*block_args| yield(*block_args) }
|
@@ -297,15 +297,15 @@ module ActiveRecord
|
|
297
297
|
end
|
298
298
|
end
|
299
299
|
|
300
|
-
def uniq
|
300
|
+
def uniq
|
301
301
|
seen = {}
|
302
|
-
|
302
|
+
load_target.find_all do |record|
|
303
303
|
seen[record.id] = true unless seen.key?(record.id)
|
304
304
|
end
|
305
305
|
end
|
306
306
|
|
307
|
-
# Replace this collection with +other_array
|
308
|
-
#
|
307
|
+
# Replace this collection with +other_array+. This will perform a diff
|
308
|
+
# and delete/add only records that have changed.
|
309
309
|
def replace(other_array)
|
310
310
|
other_array.each { |val| raise_on_type_mismatch(val) }
|
311
311
|
original_target = load_target.dup
|
@@ -323,7 +323,7 @@ module ActiveRecord
|
|
323
323
|
include_in_memory?(record)
|
324
324
|
else
|
325
325
|
load_target if options[:finder_sql]
|
326
|
-
loaded? ? target.include?(record) :
|
326
|
+
loaded? ? target.include?(record) : scope.exists?(record)
|
327
327
|
end
|
328
328
|
else
|
329
329
|
false
|
@@ -343,7 +343,7 @@ module ActiveRecord
|
|
343
343
|
callback(:before_add, record)
|
344
344
|
yield(record) if block_given?
|
345
345
|
|
346
|
-
if
|
346
|
+
if association_scope.uniq_value && index = @target.index(record)
|
347
347
|
@target[index] = record
|
348
348
|
else
|
349
349
|
@target << record
|
@@ -355,6 +355,16 @@ module ActiveRecord
|
|
355
355
|
record
|
356
356
|
end
|
357
357
|
|
358
|
+
def scope(opts = {})
|
359
|
+
scope = super()
|
360
|
+
scope.none! if opts.fetch(:nullify, true) && null_scope?
|
361
|
+
scope
|
362
|
+
end
|
363
|
+
|
364
|
+
def null_scope?
|
365
|
+
owner.new_record? && !foreign_key_present?
|
366
|
+
end
|
367
|
+
|
358
368
|
private
|
359
369
|
|
360
370
|
def custom_counter_sql
|
@@ -379,10 +389,9 @@ module ActiveRecord
|
|
379
389
|
if options[:finder_sql]
|
380
390
|
reflection.klass.find_by_sql(custom_finder_sql)
|
381
391
|
else
|
382
|
-
|
392
|
+
scope.to_a
|
383
393
|
end
|
384
394
|
|
385
|
-
records = options[:uniq] ? uniq(records) : records
|
386
395
|
records.each { |record| set_inverse_instance(record) }
|
387
396
|
records
|
388
397
|
end
|
@@ -402,12 +411,7 @@ module ActiveRecord
|
|
402
411
|
return memory if persisted.empty?
|
403
412
|
|
404
413
|
persisted.map! do |record|
|
405
|
-
|
406
|
-
# record rather than memory.at(memory.index(record)). The behavior is fixed in 1.9.
|
407
|
-
mem_index = memory.index(record)
|
408
|
-
|
409
|
-
if mem_index
|
410
|
-
mem_record = memory.delete_at(mem_index)
|
414
|
+
if mem_record = memory.delete(record)
|
411
415
|
|
412
416
|
((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
|
413
417
|
mem_record[name] = record[name]
|
@@ -422,16 +426,16 @@ module ActiveRecord
|
|
422
426
|
persisted + memory
|
423
427
|
end
|
424
428
|
|
425
|
-
def create_record(attributes,
|
429
|
+
def create_record(attributes, raise = false, &block)
|
426
430
|
unless owner.persisted?
|
427
431
|
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
|
428
432
|
end
|
429
433
|
|
430
434
|
if attributes.is_a?(Array)
|
431
|
-
attributes.collect { |attr| create_record(attr,
|
435
|
+
attributes.collect { |attr| create_record(attr, raise, &block) }
|
432
436
|
else
|
433
437
|
transaction do
|
434
|
-
add_to_target(build_record(attributes
|
438
|
+
add_to_target(build_record(attributes)) do |record|
|
435
439
|
yield(record) if block_given?
|
436
440
|
insert_record(record, true, raise)
|
437
441
|
end
|
@@ -445,7 +449,7 @@ module ActiveRecord
|
|
445
449
|
end
|
446
450
|
|
447
451
|
def create_scope
|
448
|
-
|
452
|
+
scope.scope_for_create.stringify_keys
|
449
453
|
end
|
450
454
|
|
451
455
|
def delete_or_destroy(records, method)
|
@@ -555,7 +559,7 @@ module ActiveRecord
|
|
555
559
|
# If using a custom finder_sql, #find scans the entire collection.
|
556
560
|
def find_by_scan(*args)
|
557
561
|
expects_array = args.first.kind_of?(Array)
|
558
|
-
ids = args.flatten.compact.
|
562
|
+
ids = args.flatten.compact.map{ |arg| arg.to_i }.uniq
|
559
563
|
|
560
564
|
if ids.size == 1
|
561
565
|
id = ids.first
|
@@ -570,7 +574,7 @@ module ActiveRecord
|
|
570
574
|
def first_or_last(type, *args)
|
571
575
|
args.shift if args.first.is_a?(Hash) && args.first.empty?
|
572
576
|
|
573
|
-
collection = fetch_first_or_last_using_find?(args) ?
|
577
|
+
collection = fetch_first_or_last_using_find?(args) ? scope : load_target
|
574
578
|
collection.send(type, *args).tap do |record|
|
575
579
|
set_inverse_instance record if record.is_a? ActiveRecord::Base
|
576
580
|
end
|
@@ -18,14 +18,8 @@ module ActiveRecord
|
|
18
18
|
# <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
|
19
19
|
# the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
|
20
20
|
#
|
21
|
-
# This class
|
22
|
-
#
|
23
|
-
# corner case, it even removes the +class+ method and that's why you get
|
24
|
-
#
|
25
|
-
# blog.posts.class # => Array
|
26
|
-
#
|
27
|
-
# though the object behind <tt>blog.posts</tt> is not an Array, but an
|
28
|
-
# ActiveRecord::Associations::HasManyAssociation.
|
21
|
+
# This class delegates unknown methods to <tt>@target</tt> via
|
22
|
+
# <tt>method_missing</tt>.
|
29
23
|
#
|
30
24
|
# The <tt>@target</tt> object is not \loaded until needed. For example,
|
31
25
|
#
|
@@ -33,26 +27,808 @@ module ActiveRecord
|
|
33
27
|
#
|
34
28
|
# is computed directly through SQL and does not trigger by itself the
|
35
29
|
# instantiation of the actual post records.
|
36
|
-
class CollectionProxy
|
37
|
-
|
30
|
+
class CollectionProxy < Relation
|
31
|
+
delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
|
32
|
+
|
33
|
+
def initialize(klass, association) #:nodoc:
|
34
|
+
@association = association
|
35
|
+
super klass, klass.arel_table
|
36
|
+
self.default_scoped = true
|
37
|
+
merge! association.scope(nullify: false)
|
38
|
+
end
|
39
|
+
|
40
|
+
def target
|
41
|
+
@association.target
|
42
|
+
end
|
38
43
|
|
39
|
-
|
44
|
+
def load_target
|
45
|
+
@association.load_target
|
46
|
+
end
|
40
47
|
|
41
|
-
|
42
|
-
|
48
|
+
# Returns +true+ if the association has been loaded, otherwise +false+.
|
49
|
+
#
|
50
|
+
# person.pets.loaded? # => false
|
51
|
+
# person.pets
|
52
|
+
# person.pets.loaded? # => true
|
53
|
+
def loaded?
|
54
|
+
@association.loaded?
|
55
|
+
end
|
43
56
|
|
44
|
-
|
57
|
+
# Works in two ways.
|
58
|
+
#
|
59
|
+
# *First:* Specify a subset of fields to be selected from the result set.
|
60
|
+
#
|
61
|
+
# class Person < ActiveRecord::Base
|
62
|
+
# has_many :pets
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# person.pets
|
66
|
+
# # => [
|
67
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
68
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
69
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
70
|
+
# # ]
|
71
|
+
#
|
72
|
+
# person.pets.select(:name)
|
73
|
+
# # => [
|
74
|
+
# # #<Pet id: nil, name: "Fancy-Fancy">,
|
75
|
+
# # #<Pet id: nil, name: "Spook">,
|
76
|
+
# # #<Pet id: nil, name: "Choo-Choo">
|
77
|
+
# # ]
|
78
|
+
#
|
79
|
+
# person.pets.select([:id, :name])
|
80
|
+
# # => [
|
81
|
+
# # #<Pet id: 1, name: "Fancy-Fancy">,
|
82
|
+
# # #<Pet id: 2, name: "Spook">,
|
83
|
+
# # #<Pet id: 3, name: "Choo-Choo">
|
84
|
+
# # ]
|
85
|
+
#
|
86
|
+
# Be careful because this also means you’re initializing a model
|
87
|
+
# object with only the fields that you’ve selected. If you attempt
|
88
|
+
# to access a field that is not in the initialized record you’ll
|
89
|
+
# receive:
|
90
|
+
#
|
91
|
+
# person.pets.select(:name).first.person_id
|
92
|
+
# # => ActiveModel::MissingAttributeError: missing attribute: person_id
|
93
|
+
#
|
94
|
+
# *Second:* You can pass a block so it can be used just like Array#select.
|
95
|
+
# This build an array of objects from the database for the scope,
|
96
|
+
# converting them into an array and iterating through them using
|
97
|
+
# Array#select.
|
98
|
+
#
|
99
|
+
# person.pets.select { |pet| pet.name =~ /oo/ }
|
100
|
+
# # => [
|
101
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
102
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
103
|
+
# # ]
|
104
|
+
#
|
105
|
+
# person.pets.select(:name) { |pet| pet.name =~ /oo/ }
|
106
|
+
# # => [
|
107
|
+
# # #<Pet id: 2, name: "Spook">,
|
108
|
+
# # #<Pet id: 3, name: "Choo-Choo">
|
109
|
+
# # ]
|
110
|
+
def select(select = nil, &block)
|
111
|
+
@association.select(select, &block)
|
112
|
+
end
|
45
113
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
114
|
+
# Finds an object in the collection responding to the +id+. Uses the same
|
115
|
+
# rules as <tt>ActiveRecord::Base.find</tt>. Returns <tt>ActiveRecord::RecordNotFound</tt>
|
116
|
+
# error if the object can not be found.
|
117
|
+
#
|
118
|
+
# class Person < ActiveRecord::Base
|
119
|
+
# has_many :pets
|
120
|
+
# end
|
121
|
+
#
|
122
|
+
# person.pets
|
123
|
+
# # => [
|
124
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
125
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
126
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
127
|
+
# # ]
|
128
|
+
#
|
129
|
+
# person.pets.find(1) # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
|
130
|
+
# person.pets.find(4) # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=4
|
131
|
+
#
|
132
|
+
# person.pets.find(2) { |pet| pet.name.downcase! }
|
133
|
+
# # => #<Pet id: 2, name: "fancy-fancy", person_id: 1>
|
134
|
+
#
|
135
|
+
# person.pets.find(2, 3)
|
136
|
+
# # => [
|
137
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
138
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
139
|
+
# # ]
|
140
|
+
def find(*args, &block)
|
141
|
+
@association.find(*args, &block)
|
142
|
+
end
|
52
143
|
|
53
|
-
|
54
|
-
|
55
|
-
|
144
|
+
# Returns the first record, or the first +n+ records, from the collection.
|
145
|
+
# If the collection is empty, the first form returns +nil+, and the second
|
146
|
+
# form returns an empty array.
|
147
|
+
#
|
148
|
+
# class Person < ActiveRecord::Base
|
149
|
+
# has_many :pets
|
150
|
+
# end
|
151
|
+
#
|
152
|
+
# person.pets
|
153
|
+
# # => [
|
154
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
155
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
156
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
157
|
+
# # ]
|
158
|
+
#
|
159
|
+
# person.pets.first # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
|
160
|
+
#
|
161
|
+
# person.pets.first(2)
|
162
|
+
# # => [
|
163
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
164
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>
|
165
|
+
# # ]
|
166
|
+
#
|
167
|
+
# another_person_without.pets # => []
|
168
|
+
# another_person_without.pets.first # => nil
|
169
|
+
# another_person_without.pets.first(3) # => []
|
170
|
+
def first(*args)
|
171
|
+
@association.first(*args)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Returns the last record, or the last +n+ records, from the collection.
|
175
|
+
# If the collection is empty, the first form returns +nil+, and the second
|
176
|
+
# form returns an empty array.
|
177
|
+
#
|
178
|
+
# class Person < ActiveRecord::Base
|
179
|
+
# has_many :pets
|
180
|
+
# end
|
181
|
+
#
|
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.last # => #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
190
|
+
#
|
191
|
+
# person.pets.last(2)
|
192
|
+
# # => [
|
193
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
194
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
195
|
+
# # ]
|
196
|
+
#
|
197
|
+
# another_person_without.pets # => []
|
198
|
+
# another_person_without.pets.last # => nil
|
199
|
+
# another_person_without.pets.last(3) # => []
|
200
|
+
def last(*args)
|
201
|
+
@association.last(*args)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns a new object of the collection type that has been instantiated
|
205
|
+
# with +attributes+ and linked to this object, but have not yet been saved.
|
206
|
+
# You can pass an array of attributes hashes, this will return an array
|
207
|
+
# with the new objects.
|
208
|
+
#
|
209
|
+
# class Person
|
210
|
+
# has_many :pets
|
211
|
+
# end
|
212
|
+
#
|
213
|
+
# person.pets.build
|
214
|
+
# # => #<Pet id: nil, name: nil, person_id: 1>
|
215
|
+
#
|
216
|
+
# person.pets.build(name: 'Fancy-Fancy')
|
217
|
+
# # => #<Pet id: nil, name: "Fancy-Fancy", person_id: 1>
|
218
|
+
#
|
219
|
+
# person.pets.build([{name: 'Spook'}, {name: 'Choo-Choo'}, {name: 'Brain'}])
|
220
|
+
# # => [
|
221
|
+
# # #<Pet id: nil, name: "Spook", person_id: 1>,
|
222
|
+
# # #<Pet id: nil, name: "Choo-Choo", person_id: 1>,
|
223
|
+
# # #<Pet id: nil, name: "Brain", person_id: 1>
|
224
|
+
# # ]
|
225
|
+
#
|
226
|
+
# person.pets.size # => 5 # size of the collection
|
227
|
+
# person.pets.count # => 0 # count from database
|
228
|
+
def build(attributes = {}, &block)
|
229
|
+
@association.build(attributes, &block)
|
230
|
+
end
|
231
|
+
|
232
|
+
# Returns a new object of the collection type that has been instantiated with
|
233
|
+
# attributes, linked to this object and that has already been saved (if it
|
234
|
+
# passes the validations).
|
235
|
+
#
|
236
|
+
# class Person
|
237
|
+
# has_many :pets
|
238
|
+
# end
|
239
|
+
#
|
240
|
+
# person.pets.create(name: 'Fancy-Fancy')
|
241
|
+
# # => #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>
|
242
|
+
#
|
243
|
+
# person.pets.create([{name: 'Spook'}, {name: 'Choo-Choo'}])
|
244
|
+
# # => [
|
245
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
246
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
247
|
+
# # ]
|
248
|
+
#
|
249
|
+
# person.pets.size # => 3
|
250
|
+
# person.pets.count # => 3
|
251
|
+
#
|
252
|
+
# person.pets.find(1, 2, 3)
|
253
|
+
# # => [
|
254
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
255
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
256
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
257
|
+
# # ]
|
258
|
+
def create(attributes = {}, &block)
|
259
|
+
@association.create(attributes, &block)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Like +create+, except that if the record is invalid, raises an exception.
|
263
|
+
#
|
264
|
+
# class Person
|
265
|
+
# has_many :pets
|
266
|
+
# end
|
267
|
+
#
|
268
|
+
# class Pet
|
269
|
+
# validates :name, presence: true
|
270
|
+
# end
|
271
|
+
#
|
272
|
+
# person.pets.create!(name: nil)
|
273
|
+
# # => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
|
274
|
+
def create!(attributes = {}, &block)
|
275
|
+
@association.create!(attributes, &block)
|
276
|
+
end
|
277
|
+
|
278
|
+
# Add one or more records to the collection by setting their foreign keys
|
279
|
+
# to the association's primary key. Since << flattens its argument list and
|
280
|
+
# inserts each record, +push+ and +concat+ behave identically. Returns +self+
|
281
|
+
# so method calls may be chained.
|
282
|
+
#
|
283
|
+
# class Person < ActiveRecord::Base
|
284
|
+
# pets :has_many
|
285
|
+
# end
|
286
|
+
#
|
287
|
+
# person.pets.size # => 0
|
288
|
+
# person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
|
289
|
+
# person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
|
290
|
+
# person.pets.size # => 3
|
291
|
+
#
|
292
|
+
# person.id # => 1
|
293
|
+
# person.pets
|
294
|
+
# # => [
|
295
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
296
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
297
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
298
|
+
# # ]
|
299
|
+
#
|
300
|
+
# person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
|
301
|
+
# person.pets.size # => 5
|
302
|
+
def concat(*records)
|
303
|
+
@association.concat(*records)
|
304
|
+
end
|
305
|
+
|
306
|
+
# Replace this collection with +other_array+. This will perform a diff
|
307
|
+
# and delete/add only records that have changed.
|
308
|
+
#
|
309
|
+
# class Person < ActiveRecord::Base
|
310
|
+
# has_many :pets
|
311
|
+
# end
|
312
|
+
#
|
313
|
+
# person.pets
|
314
|
+
# # => [#<Pet id: 1, name: "Gorby", group: "cats", person_id: 1>]
|
315
|
+
#
|
316
|
+
# other_pets = [Pet.new(name: 'Puff', group: 'celebrities']
|
317
|
+
#
|
318
|
+
# person.pets.replace(other_pets)
|
319
|
+
#
|
320
|
+
# person.pets
|
321
|
+
# # => [#<Pet id: 2, name: "Puff", group: "celebrities", person_id: 1>]
|
322
|
+
#
|
323
|
+
# If the supplied array has an incorrect association type, it raises
|
324
|
+
# an <tt>ActiveRecord::AssociationTypeMismatch</tt> error:
|
325
|
+
#
|
326
|
+
# person.pets.replace(["doo", "ggie", "gaga"])
|
327
|
+
# # => ActiveRecord::AssociationTypeMismatch: Pet expected, got String
|
328
|
+
def replace(other_array)
|
329
|
+
@association.replace(other_array)
|
330
|
+
end
|
331
|
+
|
332
|
+
# Deletes all the records from the collection. For +has_many+ associations,
|
333
|
+
# the deletion is done according to the strategy specified by the <tt>:dependent</tt>
|
334
|
+
# option. Returns an array with the deleted records.
|
335
|
+
#
|
336
|
+
# If no <tt>:dependent</tt> option is given, then it will follow the
|
337
|
+
# default strategy. The default strategy is <tt>:nullify</tt>. This
|
338
|
+
# sets the foreign keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>,
|
339
|
+
# the default strategy is +delete_all+.
|
340
|
+
#
|
341
|
+
# class Person < ActiveRecord::Base
|
342
|
+
# has_many :pets # dependent: :nullify option by default
|
343
|
+
# end
|
344
|
+
#
|
345
|
+
# person.pets.size # => 3
|
346
|
+
# person.pets
|
347
|
+
# # => [
|
348
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
349
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
350
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
351
|
+
# # ]
|
352
|
+
#
|
353
|
+
# person.pets.delete_all
|
354
|
+
# # => [
|
355
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
356
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
357
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
358
|
+
# # ]
|
359
|
+
#
|
360
|
+
# person.pets.size # => 0
|
361
|
+
# person.pets # => []
|
362
|
+
#
|
363
|
+
# Pet.find(1, 2, 3)
|
364
|
+
# # => [
|
365
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>,
|
366
|
+
# # #<Pet id: 2, name: "Spook", person_id: nil>,
|
367
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: nil>
|
368
|
+
# # ]
|
369
|
+
#
|
370
|
+
# If it is set to <tt>:destroy</tt> all the objects from the collection
|
371
|
+
# are removed by calling their +destroy+ method. See +destroy+ for more
|
372
|
+
# information.
|
373
|
+
#
|
374
|
+
# class Person < ActiveRecord::Base
|
375
|
+
# has_many :pets, dependent: :destroy
|
376
|
+
# end
|
377
|
+
#
|
378
|
+
# person.pets.size # => 3
|
379
|
+
# person.pets
|
380
|
+
# # => [
|
381
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
382
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
383
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
384
|
+
# # ]
|
385
|
+
#
|
386
|
+
# person.pets.delete_all
|
387
|
+
# # => [
|
388
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
389
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
390
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
391
|
+
# # ]
|
392
|
+
#
|
393
|
+
# Pet.find(1, 2, 3)
|
394
|
+
# # => ActiveRecord::RecordNotFound
|
395
|
+
#
|
396
|
+
# If it is set to <tt>:delete_all</tt>, all the objects are deleted
|
397
|
+
# *without* calling their +destroy+ method.
|
398
|
+
#
|
399
|
+
# class Person < ActiveRecord::Base
|
400
|
+
# has_many :pets, dependent: :delete_all
|
401
|
+
# end
|
402
|
+
#
|
403
|
+
# person.pets.size # => 3
|
404
|
+
# person.pets
|
405
|
+
# # => [
|
406
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
407
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
408
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
409
|
+
# # ]
|
410
|
+
#
|
411
|
+
# person.pets.delete_all
|
412
|
+
# # => [
|
413
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
414
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
415
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
416
|
+
# # ]
|
417
|
+
#
|
418
|
+
# Pet.find(1, 2, 3)
|
419
|
+
# # => ActiveRecord::RecordNotFound
|
420
|
+
def delete_all
|
421
|
+
@association.delete_all
|
422
|
+
end
|
423
|
+
|
424
|
+
# Deletes the records of the collection directly from the database.
|
425
|
+
# This will _always_ remove the records ignoring the +:dependent+
|
426
|
+
# option.
|
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.destroy_all
|
441
|
+
#
|
442
|
+
# person.pets.size # => 0
|
443
|
+
# person.pets # => []
|
444
|
+
#
|
445
|
+
# Pet.find(1) # => Couldn't find Pet with id=1
|
446
|
+
def destroy_all
|
447
|
+
@association.destroy_all
|
448
|
+
end
|
449
|
+
|
450
|
+
# Deletes the +records+ supplied and removes them from the collection. For
|
451
|
+
# +has_many+ associations, the deletion is done according to the strategy
|
452
|
+
# specified by the <tt>:dependent</tt> option. Returns an array with the
|
453
|
+
# deleted records.
|
454
|
+
#
|
455
|
+
# If no <tt>:dependent</tt> option is given, then it will follow the default
|
456
|
+
# strategy. The default strategy is <tt>:nullify</tt>. This sets the foreign
|
457
|
+
# keys to <tt>NULL</tt>. For, +has_many+ <tt>:through</tt>, the default
|
458
|
+
# strategy is +delete_all+.
|
459
|
+
#
|
460
|
+
# class Person < ActiveRecord::Base
|
461
|
+
# has_many :pets # dependent: :nullify option by default
|
462
|
+
# end
|
463
|
+
#
|
464
|
+
# person.pets.size # => 3
|
465
|
+
# person.pets
|
466
|
+
# # => [
|
467
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
468
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
469
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
470
|
+
# # ]
|
471
|
+
#
|
472
|
+
# person.pets.delete(Pet.find(1))
|
473
|
+
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
|
474
|
+
#
|
475
|
+
# person.pets.size # => 2
|
476
|
+
# person.pets
|
477
|
+
# # => [
|
478
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
479
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
480
|
+
# # ]
|
481
|
+
#
|
482
|
+
# Pet.find(1)
|
483
|
+
# # => #<Pet id: 1, name: "Fancy-Fancy", person_id: nil>
|
484
|
+
#
|
485
|
+
# If it is set to <tt>:destroy</tt> all the +records+ are removed by calling
|
486
|
+
# their +destroy+ method. See +destroy+ for more information.
|
487
|
+
#
|
488
|
+
# class Person < ActiveRecord::Base
|
489
|
+
# has_many :pets, dependent: :destroy
|
490
|
+
# end
|
491
|
+
#
|
492
|
+
# person.pets.size # => 3
|
493
|
+
# person.pets
|
494
|
+
# # => [
|
495
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
496
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
497
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
498
|
+
# # ]
|
499
|
+
#
|
500
|
+
# person.pets.delete(Pet.find(1), Pet.find(3))
|
501
|
+
# # => [
|
502
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
503
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
504
|
+
# # ]
|
505
|
+
#
|
506
|
+
# person.pets.size # => 1
|
507
|
+
# person.pets
|
508
|
+
# # => [#<Pet id: 2, name: "Spook", person_id: 1>]
|
509
|
+
#
|
510
|
+
# Pet.find(1, 3)
|
511
|
+
# # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 3)
|
512
|
+
#
|
513
|
+
# If it is set to <tt>:delete_all</tt>, all the +records+ are deleted
|
514
|
+
# *without* calling their +destroy+ method.
|
515
|
+
#
|
516
|
+
# class Person < ActiveRecord::Base
|
517
|
+
# has_many :pets, dependent: :delete_all
|
518
|
+
# end
|
519
|
+
#
|
520
|
+
# person.pets.size # => 3
|
521
|
+
# person.pets
|
522
|
+
# # => [
|
523
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
524
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
525
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
526
|
+
# # ]
|
527
|
+
#
|
528
|
+
# person.pets.delete(Pet.find(1))
|
529
|
+
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
|
530
|
+
#
|
531
|
+
# person.pets.size # => 2
|
532
|
+
# person.pets
|
533
|
+
# # => [
|
534
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
535
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
536
|
+
# # ]
|
537
|
+
#
|
538
|
+
# Pet.find(1)
|
539
|
+
# # => ActiveRecord::RecordNotFound: Couldn't find Pet with id=1
|
540
|
+
#
|
541
|
+
# You can pass +Fixnum+ or +String+ values, it finds the records
|
542
|
+
# responding to the +id+ and executes delete on them.
|
543
|
+
#
|
544
|
+
# class Person < ActiveRecord::Base
|
545
|
+
# has_many :pets
|
546
|
+
# end
|
547
|
+
#
|
548
|
+
# person.pets.size # => 3
|
549
|
+
# person.pets
|
550
|
+
# # => [
|
551
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
552
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
553
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
554
|
+
# # ]
|
555
|
+
#
|
556
|
+
# person.pets.delete("1")
|
557
|
+
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
|
558
|
+
#
|
559
|
+
# person.pets.delete(2, 3)
|
560
|
+
# # => [
|
561
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
562
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
563
|
+
# # ]
|
564
|
+
def delete(*records)
|
565
|
+
@association.delete(*records)
|
566
|
+
end
|
567
|
+
|
568
|
+
# Destroys the +records+ supplied and removes them from the collection.
|
569
|
+
# This method will _always_ remove record from the database ignoring
|
570
|
+
# the +:dependent+ option. Returns an array with the removed records.
|
571
|
+
#
|
572
|
+
# class Person < ActiveRecord::Base
|
573
|
+
# has_many :pets
|
574
|
+
# end
|
575
|
+
#
|
576
|
+
# person.pets.size # => 3
|
577
|
+
# person.pets
|
578
|
+
# # => [
|
579
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
580
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
581
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
582
|
+
# # ]
|
583
|
+
#
|
584
|
+
# person.pets.destroy(Pet.find(1))
|
585
|
+
# # => [#<Pet id: 1, name: "Fancy-Fancy", person_id: 1>]
|
586
|
+
#
|
587
|
+
# person.pets.size # => 2
|
588
|
+
# person.pets
|
589
|
+
# # => [
|
590
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
591
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
592
|
+
# # ]
|
593
|
+
#
|
594
|
+
# person.pets.destroy(Pet.find(2), Pet.find(3))
|
595
|
+
# # => [
|
596
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
597
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
598
|
+
# # ]
|
599
|
+
#
|
600
|
+
# person.pets.size # => 0
|
601
|
+
# person.pets # => []
|
602
|
+
#
|
603
|
+
# Pet.find(1, 2, 3) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (1, 2, 3)
|
604
|
+
#
|
605
|
+
# You can pass +Fixnum+ or +String+ values, it finds the records
|
606
|
+
# responding to the +id+ and then deletes them from the database.
|
607
|
+
#
|
608
|
+
# person.pets.size # => 3
|
609
|
+
# person.pets
|
610
|
+
# # => [
|
611
|
+
# # #<Pet id: 4, name: "Benny", person_id: 1>,
|
612
|
+
# # #<Pet id: 5, name: "Brain", person_id: 1>,
|
613
|
+
# # #<Pet id: 6, name: "Boss", person_id: 1>
|
614
|
+
# # ]
|
615
|
+
#
|
616
|
+
# person.pets.destroy("4")
|
617
|
+
# # => #<Pet id: 4, name: "Benny", person_id: 1>
|
618
|
+
#
|
619
|
+
# person.pets.size # => 2
|
620
|
+
# person.pets
|
621
|
+
# # => [
|
622
|
+
# # #<Pet id: 5, name: "Brain", person_id: 1>,
|
623
|
+
# # #<Pet id: 6, name: "Boss", person_id: 1>
|
624
|
+
# # ]
|
625
|
+
#
|
626
|
+
# person.pets.destroy(5, 6)
|
627
|
+
# # => [
|
628
|
+
# # #<Pet id: 5, name: "Brain", person_id: 1>,
|
629
|
+
# # #<Pet id: 6, name: "Boss", person_id: 1>
|
630
|
+
# # ]
|
631
|
+
#
|
632
|
+
# person.pets.size # => 0
|
633
|
+
# person.pets # => []
|
634
|
+
#
|
635
|
+
# Pet.find(4, 5, 6) # => ActiveRecord::RecordNotFound: Couldn't find all Pets with IDs (4, 5, 6)
|
636
|
+
def destroy(*records)
|
637
|
+
@association.destroy(*records)
|
638
|
+
end
|
639
|
+
|
640
|
+
# Specifies whether the records should be unique or not.
|
641
|
+
#
|
642
|
+
# class Person < ActiveRecord::Base
|
643
|
+
# has_many :pets
|
644
|
+
# end
|
645
|
+
#
|
646
|
+
# person.pets.select(:name)
|
647
|
+
# # => [
|
648
|
+
# # #<Pet name: "Fancy-Fancy">,
|
649
|
+
# # #<Pet name: "Fancy-Fancy">
|
650
|
+
# # ]
|
651
|
+
#
|
652
|
+
# person.pets.select(:name).uniq
|
653
|
+
# # => [#<Pet name: "Fancy-Fancy">]
|
654
|
+
def uniq
|
655
|
+
@association.uniq
|
656
|
+
end
|
657
|
+
|
658
|
+
# Count all records using SQL.
|
659
|
+
#
|
660
|
+
# class Person < ActiveRecord::Base
|
661
|
+
# has_many :pets
|
662
|
+
# end
|
663
|
+
#
|
664
|
+
# person.pets.count # => 3
|
665
|
+
# person.pets
|
666
|
+
# # => [
|
667
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
668
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
669
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
670
|
+
# # ]
|
671
|
+
def count(column_name = nil, options = {})
|
672
|
+
@association.count(column_name, options)
|
673
|
+
end
|
674
|
+
|
675
|
+
# Returns the size of the collection. If the collection hasn't been loaded,
|
676
|
+
# it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
|
677
|
+
#
|
678
|
+
# If the collection has been already loaded +size+ and +length+ are
|
679
|
+
# equivalent. If not and you are going to need the records anyway
|
680
|
+
# +length+ will take one less query. Otherwise +size+ is more efficient.
|
681
|
+
#
|
682
|
+
# class Person < ActiveRecord::Base
|
683
|
+
# has_many :pets
|
684
|
+
# end
|
685
|
+
#
|
686
|
+
# person.pets.size # => 3
|
687
|
+
# # executes something like SELECT COUNT(*) FROM "pets" WHERE "pets"."person_id" = 1
|
688
|
+
#
|
689
|
+
# person.pets # This will execute a SELECT * FROM query
|
690
|
+
# # => [
|
691
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
692
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
693
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
694
|
+
# # ]
|
695
|
+
#
|
696
|
+
# person.pets.size # => 3
|
697
|
+
# # Because the collection is already loaded, this will behave like
|
698
|
+
# # collection.size and no SQL count query is executed.
|
699
|
+
def size
|
700
|
+
@association.size
|
701
|
+
end
|
702
|
+
|
703
|
+
# Returns the size of the collection calling +size+ on the target.
|
704
|
+
# If the collection has been already loaded, +length+ and +size+ are
|
705
|
+
# equivalent. If not and you are going to need the records anyway this
|
706
|
+
# method will take one less query. Otherwise +size+ is more efficient.
|
707
|
+
#
|
708
|
+
# class Person < ActiveRecord::Base
|
709
|
+
# has_many :pets
|
710
|
+
# end
|
711
|
+
#
|
712
|
+
# person.pets.length # => 3
|
713
|
+
# # executes something like SELECT "pets".* FROM "pets" WHERE "pets"."person_id" = 1
|
714
|
+
#
|
715
|
+
# # Because the collection is loaded, you can
|
716
|
+
# # call the collection with no additional queries:
|
717
|
+
# person.pets
|
718
|
+
# # => [
|
719
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
720
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
721
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
722
|
+
# # ]
|
723
|
+
def length
|
724
|
+
@association.length
|
725
|
+
end
|
726
|
+
|
727
|
+
# Returns +true+ if the collection is empty. If the collection has been
|
728
|
+
# loaded or the <tt>:counter_sql</tt> option is provided, it is equivalent
|
729
|
+
# to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
|
730
|
+
# it is equivalent to <tt>collection.exists?</tt>. If the collection has
|
731
|
+
# not already been loaded and you are going to fetch the records anyway it
|
732
|
+
# is better to check <tt>collection.length.zero?</tt>.
|
733
|
+
#
|
734
|
+
# class Person < ActiveRecord::Base
|
735
|
+
# has_many :pets
|
736
|
+
# end
|
737
|
+
#
|
738
|
+
# person.pets.count # => 1
|
739
|
+
# person.pets.empty? # => false
|
740
|
+
#
|
741
|
+
# person.pets.delete_all
|
742
|
+
#
|
743
|
+
# person.pets.count # => 0
|
744
|
+
# person.pets.empty? # => true
|
745
|
+
def empty?
|
746
|
+
@association.empty?
|
747
|
+
end
|
748
|
+
|
749
|
+
# Returns +true+ if the collection is not empty.
|
750
|
+
#
|
751
|
+
# class Person < ActiveRecord::Base
|
752
|
+
# has_many :pets
|
753
|
+
# end
|
754
|
+
#
|
755
|
+
# person.pets.count # => 0
|
756
|
+
# person.pets.any? # => false
|
757
|
+
#
|
758
|
+
# person.pets << Pet.new(name: 'Snoop')
|
759
|
+
# person.pets.count # => 0
|
760
|
+
# person.pets.any? # => true
|
761
|
+
#
|
762
|
+
# You can also pass a block to define criteria. The behavior
|
763
|
+
# is the same, it returns true if the collection based on the
|
764
|
+
# criteria is not empty.
|
765
|
+
#
|
766
|
+
# person.pets
|
767
|
+
# # => [#<Pet name: "Snoop", group: "dogs">]
|
768
|
+
#
|
769
|
+
# person.pets.any? do |pet|
|
770
|
+
# pet.group == 'cats'
|
771
|
+
# end
|
772
|
+
# # => false
|
773
|
+
#
|
774
|
+
# person.pets.any? do |pet|
|
775
|
+
# pet.group == 'dogs'
|
776
|
+
# end
|
777
|
+
# # => true
|
778
|
+
def any?(&block)
|
779
|
+
@association.any?(&block)
|
780
|
+
end
|
781
|
+
|
782
|
+
# Returns true if the collection has more than one record.
|
783
|
+
# Equivalent to <tt>collection.size > 1</tt>.
|
784
|
+
#
|
785
|
+
# class Person < ActiveRecord::Base
|
786
|
+
# has_many :pets
|
787
|
+
# end
|
788
|
+
#
|
789
|
+
# person.pets.count #=> 1
|
790
|
+
# person.pets.many? #=> false
|
791
|
+
#
|
792
|
+
# person.pets << Pet.new(name: 'Snoopy')
|
793
|
+
# person.pets.count #=> 2
|
794
|
+
# person.pets.many? #=> true
|
795
|
+
#
|
796
|
+
# You can also pass a block to define criteria. The
|
797
|
+
# behavior is the same, it returns true if the collection
|
798
|
+
# based on the criteria has more than one record.
|
799
|
+
#
|
800
|
+
# person.pets
|
801
|
+
# # => [
|
802
|
+
# # #<Pet name: "Gorby", group: "cats">,
|
803
|
+
# # #<Pet name: "Puff", group: "cats">,
|
804
|
+
# # #<Pet name: "Snoop", group: "dogs">
|
805
|
+
# # ]
|
806
|
+
#
|
807
|
+
# person.pets.many? do |pet|
|
808
|
+
# pet.group == 'dogs'
|
809
|
+
# end
|
810
|
+
# # => false
|
811
|
+
#
|
812
|
+
# person.pets.many? do |pet|
|
813
|
+
# pet.group == 'cats'
|
814
|
+
# end
|
815
|
+
# # => true
|
816
|
+
def many?(&block)
|
817
|
+
@association.many?(&block)
|
818
|
+
end
|
819
|
+
|
820
|
+
# Returns +true+ if the given object is present in the collection.
|
821
|
+
#
|
822
|
+
# class Person < ActiveRecord::Base
|
823
|
+
# has_many :pets
|
824
|
+
# end
|
825
|
+
#
|
826
|
+
# person.pets # => [#<Pet id: 20, name: "Snoop">]
|
827
|
+
#
|
828
|
+
# person.pets.include?(Pet.find(20)) # => true
|
829
|
+
# person.pets.include?(Pet.find(21)) # => false
|
830
|
+
def include?(record)
|
831
|
+
@association.include?(record)
|
56
832
|
end
|
57
833
|
|
58
834
|
alias_method :new, :build
|
@@ -61,69 +837,143 @@ module ActiveRecord
|
|
61
837
|
@association
|
62
838
|
end
|
63
839
|
|
64
|
-
|
840
|
+
# We don't want this object to be put on the scoping stack, because
|
841
|
+
# that could create an infinite loop where we call an @association
|
842
|
+
# method, which gets the current scope, which is this object, which
|
843
|
+
# delegates to @association, and so on.
|
844
|
+
def scoping
|
845
|
+
@association.scope.scoping { yield }
|
846
|
+
end
|
847
|
+
|
848
|
+
# Returns a <tt>Relation</tt> object for the records in this association
|
849
|
+
def scope
|
65
850
|
association = @association
|
66
|
-
|
851
|
+
|
852
|
+
@association.scope.extending! do
|
67
853
|
define_method(:proxy_association) { association }
|
68
854
|
end
|
69
855
|
end
|
70
856
|
|
71
|
-
|
72
|
-
|
73
|
-
(load_target && target.respond_to?(name, include_private)) ||
|
74
|
-
proxy_association.klass.respond_to?(name, include_private)
|
75
|
-
end
|
76
|
-
|
77
|
-
def method_missing(method, *args, &block)
|
78
|
-
match = DynamicFinderMatch.match(method)
|
79
|
-
if match && match.instantiator?
|
80
|
-
send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |record|
|
81
|
-
proxy_association.send :set_owner_attributes, record
|
82
|
-
proxy_association.send :add_to_target, record
|
83
|
-
yield(record) if block_given?
|
84
|
-
end.tap do |record|
|
85
|
-
proxy_association.send :set_inverse_instance, record
|
86
|
-
end
|
87
|
-
|
88
|
-
elsif target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method))
|
89
|
-
if load_target
|
90
|
-
if target.respond_to?(method)
|
91
|
-
target.send(method, *args, &block)
|
92
|
-
else
|
93
|
-
begin
|
94
|
-
super
|
95
|
-
rescue NoMethodError => e
|
96
|
-
raise e, e.message.sub(/ for #<.*$/, " via proxy for #{target}")
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
else
|
102
|
-
scoped.readonly(nil).send(method, *args, &block)
|
103
|
-
end
|
104
|
-
end
|
857
|
+
# :nodoc:
|
858
|
+
alias spawn scope
|
105
859
|
|
106
|
-
#
|
107
|
-
#
|
108
|
-
|
109
|
-
|
860
|
+
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
|
861
|
+
# contain the same number of elements and if each element is equal
|
862
|
+
# to the corresponding element in the other array, otherwise returns
|
863
|
+
# +false+.
|
864
|
+
#
|
865
|
+
# class Person < ActiveRecord::Base
|
866
|
+
# has_many :pets
|
867
|
+
# end
|
868
|
+
#
|
869
|
+
# person.pets
|
870
|
+
# # => [
|
871
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
872
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>
|
873
|
+
# # ]
|
874
|
+
#
|
875
|
+
# other = person.pets.to_ary
|
876
|
+
#
|
877
|
+
# person.pets == other
|
878
|
+
# # => true
|
879
|
+
#
|
880
|
+
# other = [Pet.new(id: 1), Pet.new(id: 2)]
|
881
|
+
#
|
882
|
+
# person.pets == other
|
883
|
+
# # => false
|
884
|
+
def ==(other)
|
885
|
+
load_target == other
|
110
886
|
end
|
111
887
|
|
888
|
+
# Returns a new array of objects from the collection. If the collection
|
889
|
+
# hasn't been loaded, it fetches the records from the database.
|
890
|
+
#
|
891
|
+
# class Person < ActiveRecord::Base
|
892
|
+
# has_many :pets
|
893
|
+
# end
|
894
|
+
#
|
895
|
+
# person.pets
|
896
|
+
# # => [
|
897
|
+
# # #<Pet id: 4, name: "Benny", person_id: 1>,
|
898
|
+
# # #<Pet id: 5, name: "Brain", person_id: 1>,
|
899
|
+
# # #<Pet id: 6, name: "Boss", person_id: 1>
|
900
|
+
# # ]
|
901
|
+
#
|
902
|
+
# other_pets = person.pets.to_ary
|
903
|
+
# # => [
|
904
|
+
# # #<Pet id: 4, name: "Benny", person_id: 1>,
|
905
|
+
# # #<Pet id: 5, name: "Brain", person_id: 1>,
|
906
|
+
# # #<Pet id: 6, name: "Boss", person_id: 1>
|
907
|
+
# # ]
|
908
|
+
#
|
909
|
+
# other_pets.replace([Pet.new(name: 'BooGoo')])
|
910
|
+
#
|
911
|
+
# other_pets
|
912
|
+
# # => [#<Pet id: nil, name: "BooGoo", person_id: 1>]
|
913
|
+
#
|
914
|
+
# person.pets
|
915
|
+
# # This is not affected by replace
|
916
|
+
# # => [
|
917
|
+
# # #<Pet id: 4, name: "Benny", person_id: 1>,
|
918
|
+
# # #<Pet id: 5, name: "Brain", person_id: 1>,
|
919
|
+
# # #<Pet id: 6, name: "Boss", person_id: 1>
|
920
|
+
# # ]
|
112
921
|
def to_ary
|
113
922
|
load_target.dup
|
114
923
|
end
|
115
924
|
alias_method :to_a, :to_ary
|
116
925
|
|
926
|
+
# Adds one or more +records+ to the collection by setting their foreign keys
|
927
|
+
# to the association‘s primary key. Returns +self+, so several appends may be
|
928
|
+
# chained together.
|
929
|
+
#
|
930
|
+
# class Person < ActiveRecord::Base
|
931
|
+
# has_many :pets
|
932
|
+
# end
|
933
|
+
#
|
934
|
+
# person.pets.size # => 0
|
935
|
+
# person.pets << Pet.new(name: 'Fancy-Fancy')
|
936
|
+
# person.pets << [Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo')]
|
937
|
+
# person.pets.size # => 3
|
938
|
+
#
|
939
|
+
# person.id # => 1
|
940
|
+
# person.pets
|
941
|
+
# # => [
|
942
|
+
# # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
|
943
|
+
# # #<Pet id: 2, name: "Spook", person_id: 1>,
|
944
|
+
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
945
|
+
# # ]
|
117
946
|
def <<(*records)
|
118
947
|
proxy_association.concat(records) && self
|
119
948
|
end
|
120
949
|
alias_method :push, :<<
|
121
950
|
|
951
|
+
# Equivalent to +delete_all+. The difference is that returns +self+, instead
|
952
|
+
# of an array with the deleted objects, so methods can be chained. See
|
953
|
+
# +delete_all+ for more information.
|
122
954
|
def clear
|
123
955
|
delete_all
|
124
956
|
self
|
125
957
|
end
|
126
958
|
|
959
|
+
# Reloads the collection from the database. Returns +self+.
|
960
|
+
# Equivalent to <tt>collection(true)</tt>.
|
961
|
+
#
|
962
|
+
# class Person < ActiveRecord::Base
|
963
|
+
# has_many :pets
|
964
|
+
# end
|
965
|
+
#
|
966
|
+
# person.pets # fetches pets from the database
|
967
|
+
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
|
968
|
+
#
|
969
|
+
# person.pets # uses the pets cache
|
970
|
+
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
|
971
|
+
#
|
972
|
+
# person.pets.reload # fetches pets from the database
|
973
|
+
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
|
974
|
+
#
|
975
|
+
# person.pets(true) # fetches pets from the database
|
976
|
+
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
|
127
977
|
def reload
|
128
978
|
proxy_association.reload
|
129
979
|
self
|