activerecord 4.0.4 → 4.1.16
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 +1632 -1797
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +4 -0
- data/lib/active_record/associations/alias_tracker.rb +49 -29
- data/lib/active_record/associations/association.rb +9 -17
- data/lib/active_record/associations/association_scope.rb +59 -49
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +6 -1
- data/lib/active_record/associations/builder/association.rb +84 -54
- data/lib/active_record/associations/builder/belongs_to.rb +90 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +119 -25
- data/lib/active_record/associations/builder/has_many.rb +3 -3
- data/lib/active_record/associations/builder/has_one.rb +5 -7
- data/lib/active_record/associations/builder/singular_association.rb +6 -7
- data/lib/active_record/associations/collection_association.rb +121 -111
- data/lib/active_record/associations/collection_proxy.rb +73 -18
- data/lib/active_record/associations/has_many_association.rb +14 -11
- data/lib/active_record/associations/has_many_through_association.rb +33 -6
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +46 -104
- data/lib/active_record/associations/join_dependency/join_base.rb +6 -8
- data/lib/active_record/associations/join_dependency/join_part.rb +18 -37
- data/lib/active_record/associations/join_dependency.rb +208 -168
- data/lib/active_record/associations/preloader/association.rb +69 -27
- data/lib/active_record/associations/preloader/collection_association.rb +2 -2
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +58 -26
- data/lib/active_record/associations/preloader.rb +63 -49
- data/lib/active_record/associations/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +30 -9
- data/lib/active_record/associations.rb +116 -42
- data/lib/active_record/attribute_assignment.rb +6 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +35 -26
- data/lib/active_record/attribute_methods/primary_key.rb +8 -1
- data/lib/active_record/attribute_methods/read.rb +56 -29
- data/lib/active_record/attribute_methods/serialization.rb +44 -12
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +13 -1
- data/lib/active_record/attribute_methods/write.rb +59 -26
- data/lib/active_record/attribute_methods.rb +82 -43
- data/lib/active_record/autosave_association.rb +209 -194
- data/lib/active_record/base.rb +6 -2
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +5 -10
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +14 -24
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +13 -13
- data/lib/active_record/connection_adapters/abstract/quoting.rb +6 -3
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +90 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +45 -70
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -96
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +74 -66
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +231 -43
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -5
- data/lib/active_record/connection_adapters/mysql_adapter.rb +24 -17
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +22 -15
- data/lib/active_record/connection_adapters/postgresql/cast.rb +12 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -44
- data/lib/active_record/connection_adapters/postgresql/oid.rb +38 -14
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +37 -12
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +20 -11
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +98 -52
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -60
- data/lib/active_record/connection_handling.rb +39 -5
- data/lib/active_record/core.rb +38 -54
- data/lib/active_record/counter_cache.rb +9 -10
- data/lib/active_record/dynamic_matchers.rb +6 -2
- data/lib/active_record/enum.rb +199 -0
- data/lib/active_record/errors.rb +22 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +173 -76
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +23 -9
- data/lib/active_record/integration.rb +54 -1
- data/lib/active_record/locking/optimistic.rb +7 -2
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +6 -13
- data/lib/active_record/migration/command_recorder.rb +8 -2
- data/lib/active_record/migration.rb +91 -56
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +25 -13
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +26 -6
- data/lib/active_record/persistence.rb +23 -29
- data/lib/active_record/querying.rb +15 -12
- data/lib/active_record/railtie.rb +12 -61
- data/lib/active_record/railties/databases.rake +37 -56
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +230 -79
- data/lib/active_record/relation/batches.rb +74 -24
- data/lib/active_record/relation/calculations.rb +52 -48
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +210 -67
- data/lib/active_record/relation/merger.rb +15 -12
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder.rb +81 -40
- data/lib/active_record/relation/query_methods.rb +185 -108
- data/lib/active_record/relation/spawn_methods.rb +8 -5
- data/lib/active_record/relation.rb +79 -84
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +4 -4
- data/lib/active_record/schema_dumper.rb +18 -6
- data/lib/active_record/schema_migration.rb +31 -18
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +14 -29
- data/lib/active_record/scoping.rb +5 -0
- data/lib/active_record/store.rb +67 -18
- data/lib/active_record/tasks/database_tasks.rb +66 -26
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -10
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +6 -6
- data/lib/active_record/transactions.rb +10 -12
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +19 -9
- data/lib/active_record/version.rb +4 -7
- data/lib/active_record.rb +5 -7
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- data/lib/rails/generators/active_record.rb +2 -8
- metadata +18 -30
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- data/lib/active_record/associations/join_helper.rb +0 -45
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/tasks/firebird_database_tasks.rb +0 -56
- data/lib/active_record/tasks/oracle_database_tasks.rb +0 -45
- data/lib/active_record/tasks/sqlserver_database_tasks.rb +0 -48
- data/lib/active_record/test_case.rb +0 -96
@@ -5,11 +5,11 @@ module ActiveRecord::Associations::Builder
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def valid_options
|
8
|
-
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache]
|
8
|
+
super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table]
|
9
9
|
end
|
10
10
|
|
11
|
-
def valid_dependent_options
|
12
|
-
[:destroy, :delete_all, :nullify, :
|
11
|
+
def self.valid_dependent_options
|
12
|
+
[:destroy, :delete_all, :nullify, :restrict_with_error, :restrict_with_exception]
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -10,16 +10,14 @@ module ActiveRecord::Associations::Builder
|
|
10
10
|
valid
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
14
|
-
|
13
|
+
def self.valid_dependent_options
|
14
|
+
[:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
|
15
15
|
end
|
16
16
|
|
17
|
-
|
18
|
-
super unless options[:through]
|
19
|
-
end
|
17
|
+
private
|
20
18
|
|
21
|
-
def
|
22
|
-
|
19
|
+
def self.add_before_destroy_callbacks(model, reflection)
|
20
|
+
super unless reflection.options[:through]
|
23
21
|
end
|
24
22
|
end
|
25
23
|
end
|
@@ -1,19 +1,18 @@
|
|
1
|
+
# This class is inherited by the has_one and belongs_to association classes
|
2
|
+
|
1
3
|
module ActiveRecord::Associations::Builder
|
2
4
|
class SingularAssociation < Association #:nodoc:
|
3
5
|
def valid_options
|
4
6
|
super + [:remote, :dependent, :primary_key, :inverse_of]
|
5
7
|
end
|
6
8
|
|
7
|
-
def
|
8
|
-
true
|
9
|
-
end
|
10
|
-
|
11
|
-
def define_accessors
|
9
|
+
def self.define_accessors(model, reflection)
|
12
10
|
super
|
13
|
-
define_constructors if constructable?
|
11
|
+
define_constructors(model.generated_association_methods, reflection.name) if reflection.constructable?
|
14
12
|
end
|
15
13
|
|
16
|
-
|
14
|
+
# Defines the (build|create)_association methods for belongs_to or has_one association
|
15
|
+
def self.define_constructors(mixin, name)
|
17
16
|
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
|
18
17
|
def build_#{name}(*args, &block)
|
19
18
|
association(:#{name}).build(*args, &block)
|
@@ -4,10 +4,9 @@ module ActiveRecord
|
|
4
4
|
#
|
5
5
|
# CollectionAssociation is an abstract class that provides common stuff to
|
6
6
|
# ease the implementation of association proxies that represent
|
7
|
-
# collections. See the class hierarchy in
|
7
|
+
# collections. See the class hierarchy in Association.
|
8
8
|
#
|
9
9
|
# CollectionAssociation:
|
10
|
-
# HasAndBelongsToManyAssociation => has_and_belongs_to_many
|
11
10
|
# HasManyAssociation => has_many
|
12
11
|
# HasManyThroughAssociation + ThroughAssociation => has_many :through
|
13
12
|
#
|
@@ -34,7 +33,13 @@ module ActiveRecord
|
|
34
33
|
reload
|
35
34
|
end
|
36
35
|
|
37
|
-
|
36
|
+
if owner.new_record?
|
37
|
+
# Cache the proxy separately before the owner has an id
|
38
|
+
# or else a post-save proxy will still lack the id
|
39
|
+
@new_record_proxy ||= CollectionProxy.create(klass, self)
|
40
|
+
else
|
41
|
+
@proxy ||= CollectionProxy.create(klass, self)
|
42
|
+
end
|
38
43
|
end
|
39
44
|
|
40
45
|
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
|
@@ -44,7 +49,7 @@ module ActiveRecord
|
|
44
49
|
|
45
50
|
# Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
|
46
51
|
def ids_reader
|
47
|
-
if loaded?
|
52
|
+
if loaded?
|
48
53
|
load_target.map do |record|
|
49
54
|
record.send(reflection.association_primary_key)
|
50
55
|
end
|
@@ -67,11 +72,11 @@ module ActiveRecord
|
|
67
72
|
@target = []
|
68
73
|
end
|
69
74
|
|
70
|
-
def select(
|
75
|
+
def select(*fields)
|
71
76
|
if block_given?
|
72
77
|
load_target.select.each { |e| yield e }
|
73
78
|
else
|
74
|
-
scope.select(
|
79
|
+
scope.select(*fields)
|
75
80
|
end
|
76
81
|
end
|
77
82
|
|
@@ -79,12 +84,9 @@ module ActiveRecord
|
|
79
84
|
if block_given?
|
80
85
|
load_target.find(*args) { |*block_args| yield(*block_args) }
|
81
86
|
else
|
82
|
-
if options[:
|
83
|
-
find_by_scan(*args)
|
84
|
-
elsif options[:inverse_of] && loaded?
|
87
|
+
if options[:inverse_of] && loaded?
|
85
88
|
args_flatten = args.flatten
|
86
89
|
raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
|
87
|
-
|
88
90
|
result = find_by_scan(*args)
|
89
91
|
|
90
92
|
result_size = Array(result).size
|
@@ -100,11 +102,41 @@ module ActiveRecord
|
|
100
102
|
end
|
101
103
|
|
102
104
|
def first(*args)
|
103
|
-
|
105
|
+
first_nth_or_last(:first, *args)
|
106
|
+
end
|
107
|
+
|
108
|
+
def second(*args)
|
109
|
+
first_nth_or_last(:second, *args)
|
110
|
+
end
|
111
|
+
|
112
|
+
def third(*args)
|
113
|
+
first_nth_or_last(:third, *args)
|
114
|
+
end
|
115
|
+
|
116
|
+
def fourth(*args)
|
117
|
+
first_nth_or_last(:fourth, *args)
|
118
|
+
end
|
119
|
+
|
120
|
+
def fifth(*args)
|
121
|
+
first_nth_or_last(:fifth, *args)
|
122
|
+
end
|
123
|
+
|
124
|
+
def forty_two(*args)
|
125
|
+
first_nth_or_last(:forty_two, *args)
|
104
126
|
end
|
105
127
|
|
106
128
|
def last(*args)
|
107
|
-
|
129
|
+
first_nth_or_last(:last, *args)
|
130
|
+
end
|
131
|
+
|
132
|
+
def take(n = nil)
|
133
|
+
if loaded?
|
134
|
+
n ? target.take(n) : target.first
|
135
|
+
else
|
136
|
+
scope.take(n).tap do |record|
|
137
|
+
set_inverse_instance record if record.is_a? ActiveRecord::Base
|
138
|
+
end
|
139
|
+
end
|
108
140
|
end
|
109
141
|
|
110
142
|
def build(attributes = {}, &block)
|
@@ -118,11 +150,11 @@ module ActiveRecord
|
|
118
150
|
end
|
119
151
|
|
120
152
|
def create(attributes = {}, &block)
|
121
|
-
|
153
|
+
_create_record(attributes, &block)
|
122
154
|
end
|
123
155
|
|
124
156
|
def create!(attributes = {}, &block)
|
125
|
-
|
157
|
+
_create_record(attributes, true, &block)
|
126
158
|
end
|
127
159
|
|
128
160
|
# Add +records+ to this association. Returns +self+ so method calls may
|
@@ -153,11 +185,33 @@ module ActiveRecord
|
|
153
185
|
end
|
154
186
|
end
|
155
187
|
|
156
|
-
#
|
188
|
+
# Removes all records from the association without calling callbacks
|
189
|
+
# on the associated records. It honors the `:dependent` option. However
|
190
|
+
# if the `:dependent` value is `:destroy` then in that case the `:delete_all`
|
191
|
+
# deletion strategy for the association is applied.
|
192
|
+
#
|
193
|
+
# You can force a particular deletion strategy by passing a parameter.
|
194
|
+
#
|
195
|
+
# Example:
|
196
|
+
#
|
197
|
+
# @author.books.delete_all(:nullify)
|
198
|
+
# @author.books.delete_all(:delete_all)
|
157
199
|
#
|
158
200
|
# See delete for more info.
|
159
|
-
def delete_all
|
160
|
-
|
201
|
+
def delete_all(dependent = nil)
|
202
|
+
if dependent.present? && ![:nullify, :delete_all].include?(dependent)
|
203
|
+
raise ArgumentError, "Valid values are :nullify or :delete_all"
|
204
|
+
end
|
205
|
+
|
206
|
+
dependent = if dependent.present?
|
207
|
+
dependent
|
208
|
+
elsif options[:dependent] == :destroy
|
209
|
+
:delete_all
|
210
|
+
else
|
211
|
+
options[:dependent]
|
212
|
+
end
|
213
|
+
|
214
|
+
delete(:all, dependent: dependent).tap do
|
161
215
|
reset
|
162
216
|
loaded!
|
163
217
|
end
|
@@ -173,36 +227,29 @@ module ActiveRecord
|
|
173
227
|
end
|
174
228
|
end
|
175
229
|
|
176
|
-
# Count all records using SQL.
|
177
|
-
# association, it will be used for the query. Otherwise, construct options and pass them with
|
230
|
+
# Count all records using SQL. Construct options and pass them with
|
178
231
|
# scope to the target class's +count+.
|
179
232
|
def count(column_name = nil, count_options = {})
|
233
|
+
# TODO: Remove count_options argument as soon we remove support to
|
234
|
+
# activerecord-deprecated_finders.
|
180
235
|
column_name, count_options = nil, column_name if column_name.is_a?(Hash)
|
181
236
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
else
|
189
|
-
relation = scope
|
190
|
-
if association_scope.distinct_value
|
191
|
-
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
192
|
-
column_name ||= reflection.klass.primary_key
|
193
|
-
relation = relation.distinct
|
194
|
-
end
|
237
|
+
relation = scope
|
238
|
+
if association_scope.distinct_value
|
239
|
+
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
|
240
|
+
column_name ||= reflection.klass.primary_key
|
241
|
+
relation = relation.distinct
|
242
|
+
end
|
195
243
|
|
196
|
-
|
244
|
+
value = relation.count(column_name)
|
197
245
|
|
198
|
-
|
199
|
-
|
246
|
+
limit = options[:limit]
|
247
|
+
offset = options[:offset]
|
200
248
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
end
|
249
|
+
if limit || offset
|
250
|
+
[ [value - offset.to_i, 0].max, limit.to_i ].min
|
251
|
+
else
|
252
|
+
value
|
206
253
|
end
|
207
254
|
end
|
208
255
|
|
@@ -214,19 +261,11 @@ module ActiveRecord
|
|
214
261
|
# are actually removed from the database, that depends precisely on
|
215
262
|
# +delete_records+. They are in any case removed from the collection.
|
216
263
|
def delete(*records)
|
217
|
-
|
264
|
+
_options = records.extract_options!
|
265
|
+
dependent = _options[:dependent] || options[:dependent]
|
218
266
|
|
219
267
|
if records.first == :all
|
220
|
-
|
221
|
-
if dependent && dependent == :destroy
|
222
|
-
message = 'In Rails 4.1 delete_all on associations would not fire callbacks. ' \
|
223
|
-
'It means if the :dependent option is :destroy then the associated ' \
|
224
|
-
'records would be deleted without loading and invoking callbacks.'
|
225
|
-
|
226
|
-
ActiveRecord::Base.logger ? ActiveRecord::Base.logger.warn(message) : $stderr.puts(message)
|
227
|
-
end
|
228
|
-
|
229
|
-
if loaded? || dependent == :destroy
|
268
|
+
if (loaded? || dependent == :destroy) && dependent != :delete_all
|
230
269
|
delete_or_destroy(load_target, dependent)
|
231
270
|
else
|
232
271
|
delete_records(:all, dependent)
|
@@ -237,11 +276,11 @@ module ActiveRecord
|
|
237
276
|
end
|
238
277
|
end
|
239
278
|
|
240
|
-
#
|
241
|
-
# +before_remove+
|
279
|
+
# Deletes the +records+ and removes them from this association calling
|
280
|
+
# +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
|
242
281
|
#
|
243
|
-
# Note that this method
|
244
|
-
#
|
282
|
+
# Note that this method removes records from the database ignoring the
|
283
|
+
# +:dependent+ option.
|
245
284
|
def destroy(*records)
|
246
285
|
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
|
247
286
|
delete_or_destroy(records, :destroy)
|
@@ -285,14 +324,14 @@ module ActiveRecord
|
|
285
324
|
|
286
325
|
# Returns true if the collection is empty.
|
287
326
|
#
|
288
|
-
# If the collection has been loaded
|
289
|
-
#
|
327
|
+
# If the collection has been loaded
|
328
|
+
# it is equivalent to <tt>collection.size.zero?</tt>. If the
|
290
329
|
# collection has not been loaded, it is equivalent to
|
291
330
|
# <tt>collection.exists?</tt>. If the collection has not already been
|
292
331
|
# loaded and you are going to fetch the records anyway it is better to
|
293
332
|
# check <tt>collection.length.zero?</tt>.
|
294
333
|
def empty?
|
295
|
-
if loaded?
|
334
|
+
if loaded?
|
296
335
|
size.zero?
|
297
336
|
else
|
298
337
|
@target.blank? && !scope.exists?
|
@@ -345,7 +384,6 @@ module ActiveRecord
|
|
345
384
|
if record.new_record?
|
346
385
|
include_in_memory?(record)
|
347
386
|
else
|
348
|
-
load_target if options[:finder_sql]
|
349
387
|
loaded? ? target.include?(record) : scope.exists?(record)
|
350
388
|
end
|
351
389
|
else
|
@@ -362,8 +400,8 @@ module ActiveRecord
|
|
362
400
|
target
|
363
401
|
end
|
364
402
|
|
365
|
-
def add_to_target(record)
|
366
|
-
callback(:before_add, record)
|
403
|
+
def add_to_target(record, skip_callbacks = false)
|
404
|
+
callback(:before_add, record) unless skip_callbacks
|
367
405
|
yield(record) if block_given?
|
368
406
|
|
369
407
|
if association_scope.distinct_value && index = @target.index(record)
|
@@ -372,7 +410,7 @@ module ActiveRecord
|
|
372
410
|
@target << record
|
373
411
|
end
|
374
412
|
|
375
|
-
callback(:after_add, record)
|
413
|
+
callback(:after_add, record) unless skip_callbacks
|
376
414
|
set_inverse_instance(record)
|
377
415
|
|
378
416
|
record
|
@@ -390,31 +428,8 @@ module ActiveRecord
|
|
390
428
|
|
391
429
|
private
|
392
430
|
|
393
|
-
def custom_counter_sql
|
394
|
-
if options[:counter_sql]
|
395
|
-
interpolate(options[:counter_sql])
|
396
|
-
else
|
397
|
-
# replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */
|
398
|
-
interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do
|
399
|
-
count_with = $2.to_s
|
400
|
-
count_with = '*' if count_with.blank? || count_with =~ /,/ || count_with =~ /\.\*/
|
401
|
-
"SELECT #{$1}COUNT(#{count_with}) FROM"
|
402
|
-
end
|
403
|
-
end
|
404
|
-
end
|
405
|
-
|
406
|
-
def custom_finder_sql
|
407
|
-
interpolate(options[:finder_sql])
|
408
|
-
end
|
409
|
-
|
410
431
|
def find_target
|
411
|
-
records =
|
412
|
-
if options[:finder_sql]
|
413
|
-
reflection.klass.find_by_sql(custom_finder_sql)
|
414
|
-
else
|
415
|
-
scope.to_a
|
416
|
-
end
|
417
|
-
|
432
|
+
records = scope.to_a
|
418
433
|
records.each { |record| set_inverse_instance(record) }
|
419
434
|
records
|
420
435
|
end
|
@@ -449,13 +464,13 @@ module ActiveRecord
|
|
449
464
|
persisted + memory
|
450
465
|
end
|
451
466
|
|
452
|
-
def
|
467
|
+
def _create_record(attributes, raise = false, &block)
|
453
468
|
unless owner.persisted?
|
454
469
|
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
|
455
470
|
end
|
456
471
|
|
457
472
|
if attributes.is_a?(Array)
|
458
|
-
attributes.collect { |attr|
|
473
|
+
attributes.collect { |attr| _create_record(attr, raise, &block) }
|
459
474
|
else
|
460
475
|
transaction do
|
461
476
|
add_to_target(build_record(attributes)) do |record|
|
@@ -514,13 +529,13 @@ module ActiveRecord
|
|
514
529
|
target
|
515
530
|
end
|
516
531
|
|
517
|
-
def concat_records(records)
|
532
|
+
def concat_records(records, should_raise = false)
|
518
533
|
result = true
|
519
534
|
|
520
535
|
records.flatten.each do |record|
|
521
536
|
raise_on_type_mismatch!(record)
|
522
537
|
add_to_target(record) do |rec|
|
523
|
-
result &&= insert_record(rec) unless owner.new_record?
|
538
|
+
result &&= insert_record(rec, true, should_raise) unless owner.new_record?
|
524
539
|
end
|
525
540
|
end
|
526
541
|
|
@@ -529,20 +544,13 @@ module ActiveRecord
|
|
529
544
|
|
530
545
|
def callback(method, record)
|
531
546
|
callbacks_for(method).each do |callback|
|
532
|
-
|
533
|
-
when Symbol
|
534
|
-
owner.send(callback, record)
|
535
|
-
when Proc
|
536
|
-
callback.call(owner, record)
|
537
|
-
else
|
538
|
-
callback.send(method, owner, record)
|
539
|
-
end
|
547
|
+
callback.call(method, owner, record)
|
540
548
|
end
|
541
549
|
end
|
542
550
|
|
543
551
|
def callbacks_for(callback_name)
|
544
552
|
full_callback_name = "#{callback_name}_for_#{reflection.name}"
|
545
|
-
owner.class.send(full_callback_name
|
553
|
+
owner.class.send(full_callback_name)
|
546
554
|
end
|
547
555
|
|
548
556
|
# Should we deal with assoc.first or assoc.last by issuing an independent query to
|
@@ -553,33 +561,35 @@ module ActiveRecord
|
|
553
561
|
# Otherwise, go to the database only if none of the following are true:
|
554
562
|
# * target already loaded
|
555
563
|
# * owner is new record
|
556
|
-
# * custom :finder_sql exists
|
557
564
|
# * target contains new or changed record(s)
|
558
|
-
|
559
|
-
def fetch_first_or_last_using_find?(args)
|
565
|
+
def fetch_first_nth_or_last_using_find?(args)
|
560
566
|
if args.first.is_a?(Hash)
|
561
567
|
true
|
562
568
|
else
|
563
569
|
!(loaded? ||
|
564
570
|
owner.new_record? ||
|
565
|
-
|
566
|
-
target.any? { |record| record.new_record? || record.changed? } ||
|
567
|
-
args.first.kind_of?(Integer))
|
571
|
+
target.any? { |record| record.new_record? || record.changed? })
|
568
572
|
end
|
569
573
|
end
|
570
574
|
|
571
575
|
def include_in_memory?(record)
|
572
576
|
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
573
|
-
owner.
|
574
|
-
|
575
|
-
|
577
|
+
assoc = owner.association(reflection.through_reflection.name)
|
578
|
+
assoc.reader.any? { |source|
|
579
|
+
target_association = source.send(reflection.source_reflection.name)
|
580
|
+
|
581
|
+
if target_association.respond_to?(:include?)
|
582
|
+
target_association.include?(record)
|
583
|
+
else
|
584
|
+
target_association == record
|
585
|
+
end
|
576
586
|
} || target.include?(record)
|
577
587
|
else
|
578
588
|
target.include?(record)
|
579
589
|
end
|
580
590
|
end
|
581
591
|
|
582
|
-
# If
|
592
|
+
# If the :inverse_of option has been
|
583
593
|
# specified, then #find scans the entire collection.
|
584
594
|
def find_by_scan(*args)
|
585
595
|
expects_array = args.first.kind_of?(Array)
|
@@ -595,10 +605,10 @@ module ActiveRecord
|
|
595
605
|
end
|
596
606
|
|
597
607
|
# Fetches the first/last using SQL if possible, otherwise from the target array.
|
598
|
-
def
|
608
|
+
def first_nth_or_last(type, *args)
|
599
609
|
args.shift if args.first.is_a?(Hash) && args.first.empty?
|
600
610
|
|
601
|
-
collection =
|
611
|
+
collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
|
602
612
|
collection.send(type, *args).tap do |record|
|
603
613
|
set_inverse_instance record if record.is_a? ActiveRecord::Base
|
604
614
|
end
|
@@ -33,7 +33,6 @@ module ActiveRecord
|
|
33
33
|
def initialize(klass, association) #:nodoc:
|
34
34
|
@association = association
|
35
35
|
super klass, klass.arel_table
|
36
|
-
self.default_scoped = true
|
37
36
|
merge! association.scope(nullify: false)
|
38
37
|
end
|
39
38
|
|
@@ -76,7 +75,7 @@ module ActiveRecord
|
|
76
75
|
# # #<Pet id: nil, name: "Choo-Choo">
|
77
76
|
# # ]
|
78
77
|
#
|
79
|
-
# person.pets.select(
|
78
|
+
# person.pets.select(:id, :name )
|
80
79
|
# # => [
|
81
80
|
# # #<Pet id: 1, name: "Fancy-Fancy">,
|
82
81
|
# # #<Pet id: 2, name: "Spook">,
|
@@ -85,7 +84,7 @@ module ActiveRecord
|
|
85
84
|
#
|
86
85
|
# Be careful because this also means you're initializing a model
|
87
86
|
# 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
|
87
|
+
# to access a field except +id+ that is not in the initialized record you'll
|
89
88
|
# receive:
|
90
89
|
#
|
91
90
|
# person.pets.select(:name).first.person_id
|
@@ -107,13 +106,13 @@ module ActiveRecord
|
|
107
106
|
# # #<Pet id: 2, name: "Spook">,
|
108
107
|
# # #<Pet id: 3, name: "Choo-Choo">
|
109
108
|
# # ]
|
110
|
-
def select(
|
111
|
-
@association.select(
|
109
|
+
def select(*fields, &block)
|
110
|
+
@association.select(*fields, &block)
|
112
111
|
end
|
113
112
|
|
114
113
|
# Finds an object in the collection responding to the +id+. Uses the same
|
115
114
|
# rules as <tt>ActiveRecord::Base.find</tt>. Returns <tt>ActiveRecord::RecordNotFound</tt>
|
116
|
-
# error if the object
|
115
|
+
# error if the object cannot be found.
|
117
116
|
#
|
118
117
|
# class Person < ActiveRecord::Base
|
119
118
|
# has_many :pets
|
@@ -171,6 +170,32 @@ module ActiveRecord
|
|
171
170
|
@association.first(*args)
|
172
171
|
end
|
173
172
|
|
173
|
+
# Same as +first+ except returns only the second record.
|
174
|
+
def second(*args)
|
175
|
+
@association.second(*args)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Same as +first+ except returns only the third record.
|
179
|
+
def third(*args)
|
180
|
+
@association.third(*args)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Same as +first+ except returns only the fourth record.
|
184
|
+
def fourth(*args)
|
185
|
+
@association.fourth(*args)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Same as +first+ except returns only the fifth record.
|
189
|
+
def fifth(*args)
|
190
|
+
@association.fifth(*args)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Same as +first+ except returns only the forty second record.
|
194
|
+
# Also known as accessing "the reddit".
|
195
|
+
def forty_two(*args)
|
196
|
+
@association.forty_two(*args)
|
197
|
+
end
|
198
|
+
|
174
199
|
# Returns the last record, or the last +n+ records, from the collection.
|
175
200
|
# If the collection is empty, the first form returns +nil+, and the second
|
176
201
|
# form returns an empty array.
|
@@ -201,6 +226,10 @@ module ActiveRecord
|
|
201
226
|
@association.last(*args)
|
202
227
|
end
|
203
228
|
|
229
|
+
def take(n = nil)
|
230
|
+
@association.take(n)
|
231
|
+
end
|
232
|
+
|
204
233
|
# Returns a new object of the collection type that has been instantiated
|
205
234
|
# with +attributes+ and linked to this object, but have not yet been saved.
|
206
235
|
# You can pass an array of attributes hashes, this will return an array
|
@@ -418,13 +447,13 @@ module ActiveRecord
|
|
418
447
|
#
|
419
448
|
# Pet.find(1, 2, 3)
|
420
449
|
# # => ActiveRecord::RecordNotFound
|
421
|
-
def delete_all
|
422
|
-
@association.delete_all
|
450
|
+
def delete_all(dependent = nil)
|
451
|
+
@association.delete_all(dependent)
|
423
452
|
end
|
424
453
|
|
425
|
-
# Deletes the records of the collection directly from the database
|
426
|
-
#
|
427
|
-
#
|
454
|
+
# Deletes the records of the collection directly from the database
|
455
|
+
# ignoring the +:dependent+ option. It invokes +before_remove+,
|
456
|
+
# +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
|
428
457
|
#
|
429
458
|
# class Person < ActiveRecord::Base
|
430
459
|
# has_many :pets
|
@@ -671,6 +700,8 @@ module ActiveRecord
|
|
671
700
|
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
|
672
701
|
# # ]
|
673
702
|
def count(column_name = nil, options = {})
|
703
|
+
# TODO: Remove options argument as soon we remove support to
|
704
|
+
# activerecord-deprecated_finders.
|
674
705
|
@association.count(column_name, options)
|
675
706
|
end
|
676
707
|
|
@@ -727,7 +758,7 @@ module ActiveRecord
|
|
727
758
|
end
|
728
759
|
|
729
760
|
# Returns +true+ if the collection is empty. If the collection has been
|
730
|
-
# loaded
|
761
|
+
# loaded it is equivalent
|
731
762
|
# to <tt>collection.size.zero?</tt>. If the collection has not been loaded,
|
732
763
|
# it is equivalent to <tt>collection.exists?</tt>. If the collection has
|
733
764
|
# not already been loaded and you are going to fetch the records anyway it
|
@@ -788,12 +819,12 @@ module ActiveRecord
|
|
788
819
|
# has_many :pets
|
789
820
|
# end
|
790
821
|
#
|
791
|
-
# person.pets.count
|
792
|
-
# person.pets.many?
|
822
|
+
# person.pets.count # => 1
|
823
|
+
# person.pets.many? # => false
|
793
824
|
#
|
794
825
|
# person.pets << Pet.new(name: 'Snoopy')
|
795
|
-
# person.pets.count
|
796
|
-
# person.pets.many?
|
826
|
+
# person.pets.count # => 2
|
827
|
+
# person.pets.many? # => true
|
797
828
|
#
|
798
829
|
# You can also pass a block to define criteria. The
|
799
830
|
# behavior is the same, it returns true if the collection
|
@@ -833,6 +864,10 @@ module ActiveRecord
|
|
833
864
|
!!@association.include?(record)
|
834
865
|
end
|
835
866
|
|
867
|
+
def arel
|
868
|
+
scope.arel
|
869
|
+
end
|
870
|
+
|
836
871
|
def proxy_association
|
837
872
|
@association
|
838
873
|
end
|
@@ -849,8 +884,6 @@ module ActiveRecord
|
|
849
884
|
def scope
|
850
885
|
@association.scope
|
851
886
|
end
|
852
|
-
|
853
|
-
# :nodoc:
|
854
887
|
alias spawn scope
|
855
888
|
|
856
889
|
# Equivalent to <tt>Array#==</tt>. Returns +true+ if the two arrays
|
@@ -979,6 +1012,28 @@ module ActiveRecord
|
|
979
1012
|
proxy_association.reload
|
980
1013
|
self
|
981
1014
|
end
|
1015
|
+
|
1016
|
+
# Unloads the association. Returns +self+.
|
1017
|
+
#
|
1018
|
+
# class Person < ActiveRecord::Base
|
1019
|
+
# has_many :pets
|
1020
|
+
# end
|
1021
|
+
#
|
1022
|
+
# person.pets # fetches pets from the database
|
1023
|
+
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
|
1024
|
+
#
|
1025
|
+
# person.pets # uses the pets cache
|
1026
|
+
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
|
1027
|
+
#
|
1028
|
+
# person.pets.reset # clears the pets cache
|
1029
|
+
#
|
1030
|
+
# person.pets # fetches pets from the database
|
1031
|
+
# # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
|
1032
|
+
def reset
|
1033
|
+
proxy_association.reset
|
1034
|
+
proxy_association.reset_scope
|
1035
|
+
self
|
1036
|
+
end
|
982
1037
|
end
|
983
1038
|
end
|
984
1039
|
end
|