activerecord 4.0.13 → 4.1.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 +745 -2700
- data/README.rdoc +2 -2
- data/examples/performance.rb +30 -18
- data/examples/simple.rb +4 -4
- data/lib/active_record.rb +2 -6
- data/lib/active_record/aggregations.rb +2 -1
- data/lib/active_record/association_relation.rb +0 -4
- data/lib/active_record/associations.rb +87 -43
- data/lib/active_record/associations/alias_tracker.rb +1 -3
- data/lib/active_record/associations/association.rb +8 -16
- data/lib/active_record/associations/association_scope.rb +5 -16
- data/lib/active_record/associations/belongs_to_association.rb +34 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +78 -54
- data/lib/active_record/associations/builder/belongs_to.rb +91 -58
- data/lib/active_record/associations/builder/collection_association.rb +47 -45
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +107 -25
- data/lib/active_record/associations/builder/has_many.rb +2 -2
- 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 +68 -105
- data/lib/active_record/associations/collection_proxy.rb +12 -15
- data/lib/active_record/associations/has_many_association.rb +11 -9
- data/lib/active_record/associations/has_many_through_association.rb +16 -12
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +204 -165
- data/lib/active_record/associations/join_dependency/join_association.rb +43 -101
- 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_helper.rb +2 -11
- data/lib/active_record/associations/preloader.rb +89 -34
- data/lib/active_record/associations/preloader/association.rb +43 -25
- 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/singular_association.rb +6 -5
- data/lib/active_record/associations/through_association.rb +2 -2
- data/lib/active_record/attribute_assignment.rb +5 -2
- data/lib/active_record/attribute_methods.rb +45 -40
- data/lib/active_record/attribute_methods/before_type_cast.rb +2 -1
- data/lib/active_record/attribute_methods/dirty.rb +8 -22
- data/lib/active_record/attribute_methods/primary_key.rb +1 -7
- data/lib/active_record/attribute_methods/read.rb +55 -28
- data/lib/active_record/attribute_methods/serialization.rb +12 -33
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -13
- data/lib/active_record/attribute_methods/write.rb +37 -12
- data/lib/active_record/autosave_association.rb +207 -207
- data/lib/active_record/base.rb +5 -1
- data/lib/active_record/callbacks.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -7
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +11 -22
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -14
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -5
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +84 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +9 -8
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +52 -83
- data/lib/active_record/connection_adapters/abstract/transaction.rb +0 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +14 -97
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +58 -60
- data/lib/active_record/connection_adapters/column.rb +1 -35
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +3 -4
- data/lib/active_record/connection_adapters/mysql_adapter.rb +16 -15
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +24 -18
- data/lib/active_record/connection_adapters/postgresql/cast.rb +20 -16
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +23 -43
- data/lib/active_record/connection_adapters/postgresql/oid.rb +19 -12
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +28 -23
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +8 -30
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +92 -75
- data/lib/active_record/connection_adapters/schema_cache.rb +8 -29
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +31 -64
- data/lib/active_record/connection_handling.rb +2 -2
- data/lib/active_record/core.rb +22 -43
- data/lib/active_record/counter_cache.rb +7 -7
- data/lib/active_record/enum.rb +100 -0
- data/lib/active_record/errors.rb +10 -5
- data/lib/active_record/fixture_set/file.rb +2 -1
- data/lib/active_record/fixtures.rb +171 -74
- data/lib/active_record/inheritance.rb +16 -22
- data/lib/active_record/integration.rb +52 -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 +5 -12
- data/lib/active_record/migration.rb +62 -46
- data/lib/active_record/migration/command_recorder.rb +7 -13
- data/lib/active_record/model_schema.rb +7 -14
- data/lib/active_record/nested_attributes.rb +10 -8
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +3 -3
- data/lib/active_record/persistence.rb +16 -34
- data/lib/active_record/querying.rb +14 -12
- data/lib/active_record/railtie.rb +0 -50
- data/lib/active_record/railties/databases.rake +12 -15
- data/lib/active_record/readonly_attributes.rb +0 -6
- data/lib/active_record/reflection.rb +189 -75
- data/lib/active_record/relation.rb +69 -94
- data/lib/active_record/relation/batches.rb +57 -23
- data/lib/active_record/relation/calculations.rb +36 -43
- data/lib/active_record/relation/delegation.rb +54 -39
- data/lib/active_record/relation/finder_methods.rb +107 -62
- data/lib/active_record/relation/merger.rb +7 -20
- data/lib/active_record/relation/predicate_builder.rb +57 -38
- data/lib/active_record/relation/predicate_builder/array_handler.rb +29 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +110 -98
- data/lib/active_record/relation/spawn_methods.rb +1 -2
- data/lib/active_record/result.rb +45 -6
- data/lib/active_record/runtime_registry.rb +5 -0
- data/lib/active_record/sanitization.rb +6 -8
- data/lib/active_record/schema_dumper.rb +16 -5
- data/lib/active_record/schema_migration.rb +24 -25
- data/lib/active_record/scoping/default.rb +5 -18
- data/lib/active_record/scoping/named.rb +8 -29
- data/lib/active_record/store.rb +56 -28
- data/lib/active_record/tasks/database_tasks.rb +8 -4
- data/lib/active_record/timestamp.rb +4 -4
- data/lib/active_record/transactions.rb +8 -10
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +1 -6
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record.rb +2 -8
- data/lib/rails/generators/active_record/migration.rb +18 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +4 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +4 -0
- metadata +32 -45
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -65
- 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 -102
@@ -20,11 +20,5 @@ module ActiveRecord
|
|
20
20
|
self._attr_readonly
|
21
21
|
end
|
22
22
|
end
|
23
|
-
|
24
|
-
def _attr_readonly
|
25
|
-
message = "Instance level _attr_readonly method is deprecated, please use class level method."
|
26
|
-
ActiveSupport::Deprecation.warn message
|
27
|
-
defined?(@_attr_readonly) ? @_attr_readonly : self.class._attr_readonly
|
28
|
-
end
|
29
23
|
end
|
30
24
|
end
|
@@ -5,10 +5,31 @@ module ActiveRecord
|
|
5
5
|
|
6
6
|
included do
|
7
7
|
class_attribute :reflections
|
8
|
+
class_attribute :aggregate_reflections
|
8
9
|
self.reflections = {}
|
10
|
+
self.aggregate_reflections = {}
|
9
11
|
end
|
10
12
|
|
11
|
-
|
13
|
+
def self.create(macro, name, scope, options, ar)
|
14
|
+
case macro
|
15
|
+
when :has_many, :belongs_to, :has_one
|
16
|
+
klass = options[:through] ? ThroughReflection : AssociationReflection
|
17
|
+
when :composed_of
|
18
|
+
klass = AggregateReflection
|
19
|
+
end
|
20
|
+
|
21
|
+
klass.new(macro, name, scope, options, ar)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.add_reflection(ar, name, reflection)
|
25
|
+
ar.reflections = ar.reflections.merge(name => reflection)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.add_aggregate_reflection(ar, name, reflection)
|
29
|
+
ar.aggregate_reflections = ar.aggregate_reflections.merge(name => reflection)
|
30
|
+
end
|
31
|
+
|
32
|
+
# \Reflection enables to interrogate Active Record classes and objects
|
12
33
|
# about their associations and aggregations. This information can,
|
13
34
|
# for example, be used in a form builder that takes an Active Record object
|
14
35
|
# and creates input fields for all of the attributes depending on their type
|
@@ -17,22 +38,9 @@ module ActiveRecord
|
|
17
38
|
# MacroReflection class has info for AggregateReflection and AssociationReflection
|
18
39
|
# classes.
|
19
40
|
module ClassMethods
|
20
|
-
def create_reflection(macro, name, scope, options, active_record)
|
21
|
-
case macro
|
22
|
-
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
|
23
|
-
klass = options[:through] ? ThroughReflection : AssociationReflection
|
24
|
-
reflection = klass.new(macro, name, scope, options, active_record)
|
25
|
-
when :composed_of
|
26
|
-
reflection = AggregateReflection.new(macro, name, scope, options, active_record)
|
27
|
-
end
|
28
|
-
|
29
|
-
self.reflections = self.reflections.merge(name => reflection)
|
30
|
-
reflection
|
31
|
-
end
|
32
|
-
|
33
41
|
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
34
42
|
def reflect_on_all_aggregations
|
35
|
-
|
43
|
+
aggregate_reflections.values
|
36
44
|
end
|
37
45
|
|
38
46
|
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
|
@@ -40,8 +48,7 @@ module ActiveRecord
|
|
40
48
|
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
|
41
49
|
#
|
42
50
|
def reflect_on_aggregation(aggregation)
|
43
|
-
|
44
|
-
reflection if reflection.is_a?(AggregateReflection)
|
51
|
+
aggregate_reflections[aggregation]
|
45
52
|
end
|
46
53
|
|
47
54
|
# Returns an array of AssociationReflection objects for all the
|
@@ -55,7 +62,7 @@ module ActiveRecord
|
|
55
62
|
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
56
63
|
#
|
57
64
|
def reflect_on_all_associations(macro = nil)
|
58
|
-
association_reflections = reflections.values
|
65
|
+
association_reflections = reflections.values
|
59
66
|
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
60
67
|
end
|
61
68
|
|
@@ -65,8 +72,7 @@ module ActiveRecord
|
|
65
72
|
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
66
73
|
#
|
67
74
|
def reflect_on_association(association)
|
68
|
-
|
69
|
-
reflection if reflection.is_a?(AssociationReflection)
|
75
|
+
reflections[association]
|
70
76
|
end
|
71
77
|
|
72
78
|
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
@@ -100,7 +106,7 @@ module ActiveRecord
|
|
100
106
|
# Returns the hash of options used for the macro.
|
101
107
|
#
|
102
108
|
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
|
103
|
-
# <tt>has_many :clients</tt> returns
|
109
|
+
# <tt>has_many :clients</tt> returns <tt>{}</tt>
|
104
110
|
attr_reader :options
|
105
111
|
|
106
112
|
attr_reader :active_record
|
@@ -113,10 +119,16 @@ module ActiveRecord
|
|
113
119
|
@scope = scope
|
114
120
|
@options = options
|
115
121
|
@active_record = active_record
|
122
|
+
@klass = options[:class]
|
116
123
|
@plural_name = active_record.pluralize_table_names ?
|
117
124
|
name.to_s.pluralize : name.to_s
|
118
125
|
end
|
119
126
|
|
127
|
+
def autosave=(autosave)
|
128
|
+
@automatic_inverse_of = false
|
129
|
+
@options[:autosave] = autosave
|
130
|
+
end
|
131
|
+
|
120
132
|
# Returns the class for the macro.
|
121
133
|
#
|
122
134
|
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
|
@@ -178,9 +190,15 @@ module ActiveRecord
|
|
178
190
|
@klass ||= active_record.send(:compute_type, class_name)
|
179
191
|
end
|
180
192
|
|
181
|
-
|
193
|
+
attr_reader :type, :foreign_type
|
194
|
+
|
195
|
+
def initialize(macro, name, scope, options, active_record)
|
182
196
|
super
|
183
|
-
@collection =
|
197
|
+
@collection = :has_many == macro
|
198
|
+
@automatic_inverse_of = nil
|
199
|
+
@type = options[:as] && "#{options[:as]}_type"
|
200
|
+
@foreign_type = options[:foreign_type] || "#{name}_type"
|
201
|
+
@constructable = calculate_constructable(macro, options)
|
184
202
|
end
|
185
203
|
|
186
204
|
# Returns a new, unsaved instance of the associated class. +attributes+ will
|
@@ -189,12 +207,16 @@ module ActiveRecord
|
|
189
207
|
klass.new(attributes, &block)
|
190
208
|
end
|
191
209
|
|
210
|
+
def constructable? # :nodoc:
|
211
|
+
@constructable
|
212
|
+
end
|
213
|
+
|
192
214
|
def table_name
|
193
|
-
|
215
|
+
klass.table_name
|
194
216
|
end
|
195
217
|
|
196
218
|
def quoted_table_name
|
197
|
-
|
219
|
+
klass.quoted_table_name
|
198
220
|
end
|
199
221
|
|
200
222
|
def join_table
|
@@ -205,16 +227,8 @@ module ActiveRecord
|
|
205
227
|
@foreign_key ||= options[:foreign_key] || derive_foreign_key
|
206
228
|
end
|
207
229
|
|
208
|
-
def foreign_type
|
209
|
-
@foreign_type ||= options[:foreign_type] || "#{name}_type"
|
210
|
-
end
|
211
|
-
|
212
|
-
def type
|
213
|
-
@type ||= options[:as] && "#{options[:as]}_type"
|
214
|
-
end
|
215
|
-
|
216
230
|
def primary_key_column
|
217
|
-
|
231
|
+
klass.columns_hash[klass.primary_key]
|
218
232
|
end
|
219
233
|
|
220
234
|
def association_foreign_key
|
@@ -238,20 +252,8 @@ module ActiveRecord
|
|
238
252
|
end
|
239
253
|
end
|
240
254
|
|
241
|
-
def columns(tbl_name)
|
242
|
-
@columns ||= klass.connection.columns(tbl_name)
|
243
|
-
end
|
244
|
-
|
245
|
-
def reset_column_information
|
246
|
-
@columns = nil
|
247
|
-
end
|
248
|
-
|
249
255
|
def check_validity!
|
250
256
|
check_validity_of_inverse!
|
251
|
-
|
252
|
-
if has_and_belongs_to_many? && association_foreign_key == foreign_key
|
253
|
-
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
|
254
|
-
end
|
255
257
|
end
|
256
258
|
|
257
259
|
def check_validity_of_inverse!
|
@@ -267,7 +269,7 @@ module ActiveRecord
|
|
267
269
|
end
|
268
270
|
|
269
271
|
def source_reflection
|
270
|
-
|
272
|
+
self
|
271
273
|
end
|
272
274
|
|
273
275
|
# A chain of reflections from this one back to the owner. For more see the explanation in
|
@@ -289,13 +291,13 @@ module ActiveRecord
|
|
289
291
|
alias :source_macro :macro
|
290
292
|
|
291
293
|
def has_inverse?
|
292
|
-
|
294
|
+
inverse_name
|
293
295
|
end
|
294
296
|
|
295
297
|
def inverse_of
|
296
|
-
|
297
|
-
|
298
|
-
|
298
|
+
return unless inverse_name
|
299
|
+
|
300
|
+
@inverse_of ||= klass.reflect_on_association inverse_name
|
299
301
|
end
|
300
302
|
|
301
303
|
def polymorphic_inverse_of(associated_class)
|
@@ -333,10 +335,6 @@ module ActiveRecord
|
|
333
335
|
macro == :belongs_to
|
334
336
|
end
|
335
337
|
|
336
|
-
def has_and_belongs_to_many?
|
337
|
-
macro == :has_and_belongs_to_many
|
338
|
-
end
|
339
|
-
|
340
338
|
def association_class
|
341
339
|
case macro
|
342
340
|
when :belongs_to
|
@@ -345,8 +343,6 @@ module ActiveRecord
|
|
345
343
|
else
|
346
344
|
Associations::BelongsToAssociation
|
347
345
|
end
|
348
|
-
when :has_and_belongs_to_many
|
349
|
-
Associations::HasAndBelongsToManyAssociation
|
350
346
|
when :has_many
|
351
347
|
if options[:through]
|
352
348
|
Associations::HasManyThroughAssociation
|
@@ -366,11 +362,96 @@ module ActiveRecord
|
|
366
362
|
options.key? :polymorphic
|
367
363
|
end
|
368
364
|
|
365
|
+
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
|
366
|
+
INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
|
367
|
+
|
368
|
+
protected
|
369
|
+
|
370
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
371
|
+
self
|
372
|
+
end
|
373
|
+
|
369
374
|
private
|
375
|
+
|
376
|
+
def calculate_constructable(macro, options)
|
377
|
+
case macro
|
378
|
+
when :belongs_to
|
379
|
+
!options[:polymorphic]
|
380
|
+
when :has_one
|
381
|
+
!options[:through]
|
382
|
+
else
|
383
|
+
true
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
# Attempts to find the inverse association name automatically.
|
388
|
+
# If it cannot find a suitable inverse association name, it returns
|
389
|
+
# nil.
|
390
|
+
def inverse_name
|
391
|
+
options.fetch(:inverse_of) do
|
392
|
+
if @automatic_inverse_of == false
|
393
|
+
nil
|
394
|
+
else
|
395
|
+
@automatic_inverse_of ||= automatic_inverse_of
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
# returns either nil or the inverse association name that it finds.
|
401
|
+
def automatic_inverse_of
|
402
|
+
if can_find_inverse_of_automatically?(self)
|
403
|
+
inverse_name = ActiveSupport::Inflector.underscore(active_record.name).to_sym
|
404
|
+
|
405
|
+
begin
|
406
|
+
reflection = klass.reflect_on_association(inverse_name)
|
407
|
+
rescue NameError
|
408
|
+
# Give up: we couldn't compute the klass type so we won't be able
|
409
|
+
# to find any associations either.
|
410
|
+
reflection = false
|
411
|
+
end
|
412
|
+
|
413
|
+
if valid_inverse_reflection?(reflection)
|
414
|
+
return inverse_name
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
false
|
419
|
+
end
|
420
|
+
|
421
|
+
# Checks if the inverse reflection that is returned from the
|
422
|
+
# +automatic_inverse_of+ method is a valid reflection. We must
|
423
|
+
# make sure that the reflection's active_record name matches up
|
424
|
+
# with the current reflection's klass name.
|
425
|
+
#
|
426
|
+
# Note: klass will always be valid because when there's a NameError
|
427
|
+
# from calling +klass+, +reflection+ will already be set to false.
|
428
|
+
def valid_inverse_reflection?(reflection)
|
429
|
+
reflection &&
|
430
|
+
klass.name == reflection.active_record.name &&
|
431
|
+
can_find_inverse_of_automatically?(reflection)
|
432
|
+
end
|
433
|
+
|
434
|
+
# Checks to see if the reflection doesn't have any options that prevent
|
435
|
+
# us from being able to guess the inverse automatically. First, the
|
436
|
+
# <tt>inverse_of</tt> option cannot be set to false. Second, we must
|
437
|
+
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
|
438
|
+
# Third, we must not have options such as <tt>:polymorphic</tt> or
|
439
|
+
# <tt>:foreign_key</tt> which prevent us from correctly guessing the
|
440
|
+
# inverse association.
|
441
|
+
#
|
442
|
+
# Anything with a scope can additionally ruin our attempt at finding an
|
443
|
+
# inverse, so we exclude reflections with scopes.
|
444
|
+
def can_find_inverse_of_automatically?(reflection)
|
445
|
+
reflection.options[:inverse_of] != false &&
|
446
|
+
VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
|
447
|
+
!INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
|
448
|
+
!reflection.scope
|
449
|
+
end
|
450
|
+
|
370
451
|
def derive_class_name
|
371
|
-
class_name = name.to_s
|
452
|
+
class_name = name.to_s.camelize
|
372
453
|
class_name = class_name.singularize if collection?
|
373
|
-
class_name
|
454
|
+
class_name
|
374
455
|
end
|
375
456
|
|
376
457
|
def derive_foreign_key
|
@@ -384,7 +465,7 @@ module ActiveRecord
|
|
384
465
|
end
|
385
466
|
|
386
467
|
def derive_join_table
|
387
|
-
[active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*
|
468
|
+
[active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
|
388
469
|
end
|
389
470
|
|
390
471
|
def primary_key(klass)
|
@@ -398,7 +479,12 @@ module ActiveRecord
|
|
398
479
|
delegate :foreign_key, :foreign_type, :association_foreign_key,
|
399
480
|
:active_record_primary_key, :type, :to => :source_reflection
|
400
481
|
|
401
|
-
|
482
|
+
def initialize(macro, name, scope, options, active_record)
|
483
|
+
super
|
484
|
+
@source_reflection_name = options[:source]
|
485
|
+
end
|
486
|
+
|
487
|
+
# Returns the source of the through reflection. It checks both a singularized
|
402
488
|
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
|
403
489
|
#
|
404
490
|
# class Post < ActiveRecord::Base
|
@@ -412,12 +498,11 @@ module ActiveRecord
|
|
412
498
|
# end
|
413
499
|
#
|
414
500
|
# tags_reflection = Post.reflect_on_association(:tags)
|
415
|
-
#
|
416
|
-
# taggings_reflection = tags_reflection.source_reflection
|
501
|
+
# tags_reflection.source_reflection
|
417
502
|
# # => <ActiveRecord::Reflection::AssociationReflection: @macro=:belongs_to, @name=:tag, @active_record=Tagging, @plural_name="tags">
|
418
503
|
#
|
419
504
|
def source_reflection
|
420
|
-
|
505
|
+
through_reflection.klass.reflect_on_association(source_reflection_name)
|
421
506
|
end
|
422
507
|
|
423
508
|
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
|
@@ -429,10 +514,11 @@ module ActiveRecord
|
|
429
514
|
# end
|
430
515
|
#
|
431
516
|
# tags_reflection = Post.reflect_on_association(:tags)
|
432
|
-
#
|
517
|
+
# tags_reflection.through_reflection
|
518
|
+
# # => <ActiveRecord::Reflection::AssociationReflection: @macro=:has_many, @name=:taggings, @active_record=Post, @plural_name="taggings">
|
433
519
|
#
|
434
520
|
def through_reflection
|
435
|
-
|
521
|
+
active_record.reflect_on_association(options[:through])
|
436
522
|
end
|
437
523
|
|
438
524
|
# Returns an array of reflections which are involved in this association. Each item in the
|
@@ -454,7 +540,9 @@ module ActiveRecord
|
|
454
540
|
#
|
455
541
|
def chain
|
456
542
|
@chain ||= begin
|
457
|
-
|
543
|
+
a = source_reflection.chain
|
544
|
+
b = through_reflection.chain
|
545
|
+
chain = a + b
|
458
546
|
chain[0] = self # Use self so we don't lose the information from :source_type
|
459
547
|
chain
|
460
548
|
end
|
@@ -505,7 +593,7 @@ module ActiveRecord
|
|
505
593
|
|
506
594
|
# A through association is nested if there would be more than one join table
|
507
595
|
def nested?
|
508
|
-
chain.length > 2
|
596
|
+
chain.length > 2
|
509
597
|
end
|
510
598
|
|
511
599
|
# We want to use the klass from this reflection, rather than just delegate straight to
|
@@ -514,12 +602,7 @@ module ActiveRecord
|
|
514
602
|
def association_primary_key(klass = nil)
|
515
603
|
# Get the "actual" source reflection if the immediate source reflection has a
|
516
604
|
# source reflection itself
|
517
|
-
|
518
|
-
while source_reflection.source_reflection
|
519
|
-
source_reflection = source_reflection.source_reflection
|
520
|
-
end
|
521
|
-
|
522
|
-
source_reflection.options[:primary_key] || primary_key(klass || self.klass)
|
605
|
+
actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
|
523
606
|
end
|
524
607
|
|
525
608
|
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
|
@@ -534,7 +617,32 @@ module ActiveRecord
|
|
534
617
|
# # => [:tag, :tags]
|
535
618
|
#
|
536
619
|
def source_reflection_names
|
537
|
-
|
620
|
+
(options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }.uniq
|
621
|
+
end
|
622
|
+
|
623
|
+
def source_reflection_name # :nodoc:
|
624
|
+
return @source_reflection_name if @source_reflection_name
|
625
|
+
|
626
|
+
names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
|
627
|
+
names = names.find_all { |n|
|
628
|
+
through_reflection.klass.reflect_on_association(n)
|
629
|
+
}
|
630
|
+
|
631
|
+
if names.length > 1
|
632
|
+
example_options = options.dup
|
633
|
+
example_options[:source] = source_reflection_names.first
|
634
|
+
ActiveSupport::Deprecation.warn <<-eowarn
|
635
|
+
Ambiguous source reflection for through association. Please specify a :source
|
636
|
+
directive on your declaration like:
|
637
|
+
|
638
|
+
class #{active_record.name} < ActiveRecord::Base
|
639
|
+
#{macro} :#{name}, #{example_options}
|
640
|
+
end
|
641
|
+
|
642
|
+
eowarn
|
643
|
+
end
|
644
|
+
|
645
|
+
@source_reflection_name = names.first
|
538
646
|
end
|
539
647
|
|
540
648
|
def source_options
|
@@ -573,6 +681,12 @@ module ActiveRecord
|
|
573
681
|
check_validity_of_inverse!
|
574
682
|
end
|
575
683
|
|
684
|
+
protected
|
685
|
+
|
686
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
687
|
+
source_reflection.actual_source_reflection
|
688
|
+
end
|
689
|
+
|
576
690
|
private
|
577
691
|
def derive_class_name
|
578
692
|
# get the class_name of the belongs_to association of the through reflection
|