activerecord 3.2.22.5 → 4.2.11.3
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 +5 -5
- data/CHANGELOG.md +1632 -609
- data/MIT-LICENSE +1 -1
- data/README.rdoc +37 -41
- data/examples/performance.rb +31 -19
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +56 -42
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -36
- data/lib/active_record/associations/association.rb +73 -55
- data/lib/active_record/associations/association_scope.rb +143 -82
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +125 -31
- data/lib/active_record/associations/builder/belongs_to.rb +89 -61
- data/lib/active_record/associations/builder/collection_association.rb +69 -49
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +12 -51
- data/lib/active_record/associations/builder/singular_association.rb +23 -17
- data/lib/active_record/associations/collection_association.rb +251 -177
- data/lib/active_record/associations/collection_proxy.rb +963 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +113 -22
- data/lib/active_record/associations/has_many_through_association.rb +99 -39
- data/lib/active_record/associations/has_one_association.rb +43 -20
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +230 -156
- data/lib/active_record/associations/preloader/association.rb +96 -55
- data/lib/active_record/associations/preloader/collection_association.rb +3 -3
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +62 -33
- data/lib/active_record/associations/preloader.rb +101 -79
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +30 -16
- data/lib/active_record/associations.rb +463 -345
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +142 -151
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +137 -57
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +73 -106
- data/lib/active_record/attribute_methods/serialization.rb +44 -94
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
- data/lib/active_record/attribute_methods/write.rb +57 -44
- data/lib/active_record/attribute_methods.rb +301 -141
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +246 -217
- data/lib/active_record/base.rb +70 -474
- data/lib/active_record/callbacks.rb +66 -28
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
- data/lib/active_record/connection_adapters/column.rb +31 -245
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
- data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
- data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +157 -105
- data/lib/active_record/dynamic_matchers.rb +119 -63
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +94 -36
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +9 -5
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +302 -215
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +143 -70
- data/lib/active_record/integration.rb +65 -12
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +73 -52
- data/lib/active_record/locking/pessimistic.rb +5 -5
- data/lib/active_record/log_subscriber.rb +24 -21
- data/lib/active_record/migration/command_recorder.rb +124 -32
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +511 -213
- data/lib/active_record/model_schema.rb +91 -117
- data/lib/active_record/nested_attributes.rb +184 -130
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +276 -117
- data/lib/active_record/query_cache.rb +19 -37
- data/lib/active_record/querying.rb +28 -18
- data/lib/active_record/railtie.rb +73 -40
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +141 -416
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +1 -4
- data/lib/active_record/reflection.rb +513 -154
- data/lib/active_record/relation/batches.rb +91 -43
- data/lib/active_record/relation/calculations.rb +199 -161
- data/lib/active_record/relation/delegation.rb +116 -25
- data/lib/active_record/relation/finder_methods.rb +362 -248
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +135 -43
- data/lib/active_record/relation/query_methods.rb +928 -167
- data/lib/active_record/relation/spawn_methods.rb +48 -149
- data/lib/active_record/relation.rb +352 -207
- data/lib/active_record/result.rb +101 -10
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +56 -59
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +106 -63
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +50 -57
- data/lib/active_record/scoping/named.rb +73 -109
- data/lib/active_record/scoping.rb +58 -123
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +12 -22
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +168 -15
- data/lib/active_record/tasks/database_tasks.rb +299 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +23 -16
- data/lib/active_record/transactions.rb +125 -79
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +24 -16
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +123 -64
- data/lib/active_record/validations.rb +36 -29
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +66 -46
- data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +101 -45
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- 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/active_record/test_case.rb +0 -73
- 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,5 +1,5 @@
|
|
1
|
-
require '
|
2
|
-
require 'active_support/core_ext/
|
1
|
+
require 'thread'
|
2
|
+
require 'active_support/core_ext/string/filters'
|
3
3
|
|
4
4
|
module ActiveRecord
|
5
5
|
# = Active Record Reflection
|
@@ -7,11 +7,40 @@ module ActiveRecord
|
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
9
|
included do
|
10
|
-
class_attribute :
|
11
|
-
|
10
|
+
class_attribute :_reflections, instance_writer: false
|
11
|
+
class_attribute :aggregate_reflections, instance_writer: false
|
12
|
+
self._reflections = {}
|
13
|
+
self.aggregate_reflections = {}
|
12
14
|
end
|
13
15
|
|
14
|
-
|
16
|
+
def self.create(macro, name, scope, options, ar)
|
17
|
+
klass = case macro
|
18
|
+
when :composed_of
|
19
|
+
AggregateReflection
|
20
|
+
when :has_many
|
21
|
+
HasManyReflection
|
22
|
+
when :has_one
|
23
|
+
HasOneReflection
|
24
|
+
when :belongs_to
|
25
|
+
BelongsToReflection
|
26
|
+
else
|
27
|
+
raise "Unsupported Macro: #{macro}"
|
28
|
+
end
|
29
|
+
|
30
|
+
reflection = klass.new(name, scope, options, ar)
|
31
|
+
options[:through] ? ThroughReflection.new(reflection) : reflection
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.add_reflection(ar, name, reflection)
|
35
|
+
ar.clear_reflections_cache
|
36
|
+
ar._reflections = ar._reflections.merge(name.to_s => reflection)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.add_aggregate_reflection(ar, name, reflection)
|
40
|
+
ar.aggregate_reflections = ar.aggregate_reflections.merge(name.to_s => reflection)
|
41
|
+
end
|
42
|
+
|
43
|
+
# \Reflection enables interrogating of Active Record classes and objects
|
15
44
|
# about their associations and aggregations. This information can,
|
16
45
|
# for example, be used in a form builder that takes an Active Record object
|
17
46
|
# and creates input fields for all of the attributes depending on their type
|
@@ -20,22 +49,9 @@ module ActiveRecord
|
|
20
49
|
# MacroReflection class has info for AggregateReflection and AssociationReflection
|
21
50
|
# classes.
|
22
51
|
module ClassMethods
|
23
|
-
def create_reflection(macro, name, options, active_record)
|
24
|
-
case macro
|
25
|
-
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
|
26
|
-
klass = options[:through] ? ThroughReflection : AssociationReflection
|
27
|
-
reflection = klass.new(macro, name, options, active_record)
|
28
|
-
when :composed_of
|
29
|
-
reflection = AggregateReflection.new(macro, name, options, active_record)
|
30
|
-
end
|
31
|
-
|
32
|
-
self.reflections = self.reflections.merge(name => reflection)
|
33
|
-
reflection
|
34
|
-
end
|
35
|
-
|
36
52
|
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
37
53
|
def reflect_on_all_aggregations
|
38
|
-
|
54
|
+
aggregate_reflections.values
|
39
55
|
end
|
40
56
|
|
41
57
|
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
|
@@ -43,7 +59,30 @@ module ActiveRecord
|
|
43
59
|
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
|
44
60
|
#
|
45
61
|
def reflect_on_aggregation(aggregation)
|
46
|
-
|
62
|
+
aggregate_reflections[aggregation.to_s]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns a Hash of name of the reflection as the key and a AssociationReflection as the value.
|
66
|
+
#
|
67
|
+
# Account.reflections # => {"balance" => AggregateReflection}
|
68
|
+
#
|
69
|
+
# @api public
|
70
|
+
def reflections
|
71
|
+
@__reflections ||= begin
|
72
|
+
ref = {}
|
73
|
+
|
74
|
+
_reflections.each do |name, reflection|
|
75
|
+
parent_name, parent_reflection = reflection.parent_reflection
|
76
|
+
|
77
|
+
if parent_name
|
78
|
+
ref[parent_name] = parent_reflection
|
79
|
+
else
|
80
|
+
ref[name] = reflection
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
ref
|
85
|
+
end
|
47
86
|
end
|
48
87
|
|
49
88
|
# Returns an array of AssociationReflection objects for all the
|
@@ -56,8 +95,9 @@ module ActiveRecord
|
|
56
95
|
# Account.reflect_on_all_associations # returns an array of all associations
|
57
96
|
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
58
97
|
#
|
98
|
+
# @api public
|
59
99
|
def reflect_on_all_associations(macro = nil)
|
60
|
-
association_reflections = reflections.values
|
100
|
+
association_reflections = reflections.values
|
61
101
|
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
62
102
|
end
|
63
103
|
|
@@ -66,65 +106,144 @@ module ActiveRecord
|
|
66
106
|
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
67
107
|
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
68
108
|
#
|
109
|
+
# @api public
|
69
110
|
def reflect_on_association(association)
|
70
|
-
reflections[association
|
111
|
+
reflections[association.to_s]
|
112
|
+
end
|
113
|
+
|
114
|
+
# @api private
|
115
|
+
def _reflect_on_association(association) #:nodoc:
|
116
|
+
_reflections[association.to_s]
|
71
117
|
end
|
72
118
|
|
73
119
|
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
120
|
+
#
|
121
|
+
# @api public
|
74
122
|
def reflect_on_all_autosave_associations
|
75
123
|
reflections.values.select { |reflection| reflection.options[:autosave] }
|
76
124
|
end
|
125
|
+
|
126
|
+
def clear_reflections_cache #:nodoc:
|
127
|
+
@__reflections = nil
|
128
|
+
end
|
77
129
|
end
|
78
130
|
|
131
|
+
# Holds all the methods that are shared between MacroReflection, AssociationReflection
|
132
|
+
# and ThroughReflection
|
133
|
+
class AbstractReflection # :nodoc:
|
134
|
+
def table_name
|
135
|
+
klass.table_name
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns a new, unsaved instance of the associated class. +attributes+ will
|
139
|
+
# be passed to the class's constructor.
|
140
|
+
def build_association(attributes, &block)
|
141
|
+
klass.new(attributes, &block)
|
142
|
+
end
|
143
|
+
|
144
|
+
def quoted_table_name
|
145
|
+
klass.quoted_table_name
|
146
|
+
end
|
147
|
+
|
148
|
+
def primary_key_type
|
149
|
+
klass.type_for_attribute(klass.primary_key)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns the class name for the macro.
|
153
|
+
#
|
154
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
|
155
|
+
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
|
156
|
+
def class_name
|
157
|
+
@class_name ||= (options[:class_name] || derive_class_name).to_s
|
158
|
+
end
|
159
|
+
|
160
|
+
JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
|
161
|
+
|
162
|
+
def join_keys(assoc_klass)
|
163
|
+
JoinKeys.new(foreign_key, active_record_primary_key)
|
164
|
+
end
|
79
165
|
|
80
|
-
|
166
|
+
def source_macro
|
167
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
168
|
+
ActiveRecord::Base.source_macro is deprecated and will be removed
|
169
|
+
without replacement.
|
170
|
+
MSG
|
171
|
+
|
172
|
+
macro
|
173
|
+
end
|
174
|
+
|
175
|
+
def inverse_of
|
176
|
+
return unless inverse_name
|
177
|
+
|
178
|
+
@inverse_of ||= klass._reflect_on_association inverse_name
|
179
|
+
end
|
180
|
+
|
181
|
+
def check_validity_of_inverse!
|
182
|
+
unless polymorphic?
|
183
|
+
if has_inverse? && inverse_of.nil?
|
184
|
+
raise InverseOfAssociationNotFoundError.new(self)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
# Base class for AggregateReflection and AssociationReflection. Objects of
|
81
190
|
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
82
|
-
|
191
|
+
#
|
192
|
+
# MacroReflection
|
193
|
+
# AssociationReflection
|
194
|
+
# AggregateReflection
|
195
|
+
# HasManyReflection
|
196
|
+
# HasOneReflection
|
197
|
+
# BelongsToReflection
|
198
|
+
# ThroughReflection
|
199
|
+
class MacroReflection < AbstractReflection
|
83
200
|
# Returns the name of the macro.
|
84
201
|
#
|
85
|
-
# <tt>composed_of :balance, :
|
202
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
|
86
203
|
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
|
87
204
|
attr_reader :name
|
88
205
|
|
89
|
-
|
90
|
-
#
|
91
|
-
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
|
92
|
-
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
|
93
|
-
attr_reader :macro
|
206
|
+
attr_reader :scope
|
94
207
|
|
95
208
|
# Returns the hash of options used for the macro.
|
96
209
|
#
|
97
|
-
# <tt>composed_of :balance, :
|
98
|
-
# <tt>has_many :clients</tt> returns
|
210
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
|
211
|
+
# <tt>has_many :clients</tt> returns <tt>{}</tt>
|
99
212
|
attr_reader :options
|
100
213
|
|
101
214
|
attr_reader :active_record
|
102
215
|
|
103
216
|
attr_reader :plural_name # :nodoc:
|
104
217
|
|
105
|
-
def initialize(
|
106
|
-
@macro = macro
|
218
|
+
def initialize(name, scope, options, active_record)
|
107
219
|
@name = name
|
220
|
+
@scope = scope
|
108
221
|
@options = options
|
109
222
|
@active_record = active_record
|
223
|
+
@klass = options[:anonymous_class]
|
110
224
|
@plural_name = active_record.pluralize_table_names ?
|
111
225
|
name.to_s.pluralize : name.to_s
|
112
226
|
end
|
113
227
|
|
228
|
+
def autosave=(autosave)
|
229
|
+
@automatic_inverse_of = false
|
230
|
+
@options[:autosave] = autosave
|
231
|
+
_, parent_reflection = self.parent_reflection
|
232
|
+
if parent_reflection
|
233
|
+
parent_reflection.autosave = autosave
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
114
237
|
# Returns the class for the macro.
|
115
238
|
#
|
116
|
-
# <tt>composed_of :balance, :
|
239
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
|
117
240
|
# <tt>has_many :clients</tt> returns the Client class
|
118
241
|
def klass
|
119
|
-
@klass ||= class_name
|
242
|
+
@klass ||= compute_class(class_name)
|
120
243
|
end
|
121
244
|
|
122
|
-
|
123
|
-
|
124
|
-
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
|
125
|
-
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
|
126
|
-
def class_name
|
127
|
-
@class_name ||= (options[:class_name] || derive_class_name).to_s
|
245
|
+
def compute_class(name)
|
246
|
+
name.constantize
|
128
247
|
end
|
129
248
|
|
130
249
|
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
|
@@ -133,14 +252,10 @@ module ActiveRecord
|
|
133
252
|
super ||
|
134
253
|
other_aggregation.kind_of?(self.class) &&
|
135
254
|
name == other_aggregation.name &&
|
136
|
-
other_aggregation.options &&
|
255
|
+
!other_aggregation.options.nil? &&
|
137
256
|
active_record == other_aggregation.active_record
|
138
257
|
end
|
139
258
|
|
140
|
-
def sanitized_conditions #:nodoc:
|
141
|
-
@sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
142
|
-
end
|
143
|
-
|
144
259
|
private
|
145
260
|
def derive_class_name
|
146
261
|
name.to_s.camelize
|
@@ -151,6 +266,10 @@ module ActiveRecord
|
|
151
266
|
# Holds all the meta-data about an aggregation as it was specified in the
|
152
267
|
# Active Record class.
|
153
268
|
class AggregateReflection < MacroReflection #:nodoc:
|
269
|
+
def mapping
|
270
|
+
mapping = options[:mapping] || [name, name]
|
271
|
+
mapping.first.is_a?(Array) ? mapping : [mapping]
|
272
|
+
end
|
154
273
|
end
|
155
274
|
|
156
275
|
# Holds all the meta-data about an association as it was specified in the
|
@@ -169,42 +288,46 @@ module ActiveRecord
|
|
169
288
|
# a new association object. Use +build_association+ or +create_association+
|
170
289
|
# instead. This allows plugins to hook into association object creation.
|
171
290
|
def klass
|
172
|
-
@klass ||=
|
291
|
+
@klass ||= compute_class(class_name)
|
173
292
|
end
|
174
293
|
|
175
|
-
def
|
176
|
-
|
177
|
-
@collection = macro.in?([:has_many, :has_and_belongs_to_many])
|
294
|
+
def compute_class(name)
|
295
|
+
active_record.send(:compute_type, name)
|
178
296
|
end
|
179
297
|
|
180
|
-
|
181
|
-
|
182
|
-
def build_association(*options, &block)
|
183
|
-
klass.new(*options, &block)
|
184
|
-
end
|
298
|
+
attr_reader :type, :foreign_type
|
299
|
+
attr_accessor :parent_reflection # [:name, Reflection]
|
185
300
|
|
186
|
-
def
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
@
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
301
|
+
def initialize(name, scope, options, active_record)
|
302
|
+
super
|
303
|
+
@automatic_inverse_of = nil
|
304
|
+
@type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
|
305
|
+
@foreign_type = options[:foreign_type] || "#{name}_type"
|
306
|
+
@constructable = calculate_constructable(macro, options)
|
307
|
+
@association_scope_cache = {}
|
308
|
+
@scope_lock = Mutex.new
|
309
|
+
end
|
310
|
+
|
311
|
+
def association_scope_cache(conn, owner)
|
312
|
+
key = conn.prepared_statements
|
313
|
+
if polymorphic?
|
314
|
+
key = [key, owner._read_attribute(@foreign_type)]
|
315
|
+
end
|
316
|
+
@association_scope_cache[key] ||= @scope_lock.synchronize {
|
317
|
+
@association_scope_cache[key] ||= yield
|
318
|
+
}
|
196
319
|
end
|
197
320
|
|
198
|
-
def
|
199
|
-
@
|
321
|
+
def constructable? # :nodoc:
|
322
|
+
@constructable
|
200
323
|
end
|
201
324
|
|
202
|
-
def
|
203
|
-
@
|
325
|
+
def join_table
|
326
|
+
@join_table ||= options[:join_table] || derive_join_table
|
204
327
|
end
|
205
328
|
|
206
|
-
def
|
207
|
-
@
|
329
|
+
def foreign_key
|
330
|
+
@foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
|
208
331
|
end
|
209
332
|
|
210
333
|
def association_foreign_key
|
@@ -228,32 +351,36 @@ module ActiveRecord
|
|
228
351
|
end
|
229
352
|
end
|
230
353
|
|
231
|
-
def columns(tbl_name, log_msg)
|
232
|
-
@columns ||= klass.connection.columns(tbl_name, log_msg)
|
233
|
-
end
|
234
|
-
|
235
|
-
def reset_column_information
|
236
|
-
@columns = nil
|
237
|
-
end
|
238
|
-
|
239
354
|
def check_validity!
|
240
355
|
check_validity_of_inverse!
|
241
356
|
end
|
242
357
|
|
243
|
-
def
|
244
|
-
unless
|
245
|
-
|
246
|
-
|
247
|
-
|
358
|
+
def check_preloadable!
|
359
|
+
return unless scope
|
360
|
+
|
361
|
+
if scope.arity > 0
|
362
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
363
|
+
The association scope '#{name}' is instance dependent (the scope
|
364
|
+
block takes an argument). Preloading happens before the individual
|
365
|
+
instances are created. This means that there is no instance being
|
366
|
+
passed to the association scope. This will most likely result in
|
367
|
+
broken or incorrect behavior. Joining, Preloading and eager loading
|
368
|
+
of these associations is deprecated and will be removed in the future.
|
369
|
+
MSG
|
248
370
|
end
|
249
371
|
end
|
372
|
+
alias :check_eager_loadable! :check_preloadable!
|
373
|
+
|
374
|
+
def join_id_for(owner) # :nodoc:
|
375
|
+
owner[active_record_primary_key]
|
376
|
+
end
|
250
377
|
|
251
378
|
def through_reflection
|
252
379
|
nil
|
253
380
|
end
|
254
381
|
|
255
382
|
def source_reflection
|
256
|
-
|
383
|
+
self
|
257
384
|
end
|
258
385
|
|
259
386
|
# A chain of reflections from this one back to the owner. For more see the explanation in
|
@@ -266,28 +393,19 @@ module ActiveRecord
|
|
266
393
|
false
|
267
394
|
end
|
268
395
|
|
269
|
-
# An array of arrays of
|
270
|
-
# in the #chain.
|
271
|
-
|
272
|
-
|
273
|
-
[[options[:conditions]].compact]
|
396
|
+
# An array of arrays of scopes. Each item in the outside array corresponds to a reflection
|
397
|
+
# in the #chain.
|
398
|
+
def scope_chain
|
399
|
+
scope ? [[scope]] : [[]]
|
274
400
|
end
|
275
401
|
|
276
|
-
alias :source_macro :macro
|
277
|
-
|
278
402
|
def has_inverse?
|
279
|
-
|
280
|
-
end
|
281
|
-
|
282
|
-
def inverse_of
|
283
|
-
if has_inverse?
|
284
|
-
@inverse_of ||= klass.reflect_on_association(options[:inverse_of])
|
285
|
-
end
|
403
|
+
inverse_name
|
286
404
|
end
|
287
405
|
|
288
406
|
def polymorphic_inverse_of(associated_class)
|
289
407
|
if has_inverse?
|
290
|
-
if inverse_relationship = associated_class.
|
408
|
+
if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
|
291
409
|
inverse_relationship
|
292
410
|
else
|
293
411
|
raise InverseOfAssociationNotFoundError.new(self, associated_class)
|
@@ -295,41 +413,45 @@ module ActiveRecord
|
|
295
413
|
end
|
296
414
|
end
|
297
415
|
|
416
|
+
# Returns the macro type.
|
417
|
+
#
|
418
|
+
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
|
419
|
+
def macro; raise NotImplementedError; end
|
420
|
+
|
298
421
|
# Returns whether or not this association reflection is for a collection
|
299
422
|
# association. Returns +true+ if the +macro+ is either +has_many+ or
|
300
423
|
# +has_and_belongs_to_many+, +false+ otherwise.
|
301
424
|
def collection?
|
302
|
-
|
425
|
+
false
|
303
426
|
end
|
304
427
|
|
305
428
|
# Returns whether or not the association should be validated as part of
|
306
429
|
# the parent's validation.
|
307
430
|
#
|
308
431
|
# Unless you explicitly disable validation with
|
309
|
-
# <tt
|
432
|
+
# <tt>validate: false</tt>, validation will take place when:
|
310
433
|
#
|
311
|
-
# * you explicitly enable validation; <tt
|
312
|
-
# * you use autosave; <tt
|
434
|
+
# * you explicitly enable validation; <tt>validate: true</tt>
|
435
|
+
# * you use autosave; <tt>autosave: true</tt>
|
313
436
|
# * the association is a +has_many+ association
|
314
437
|
def validate?
|
315
|
-
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true ||
|
438
|
+
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
|
316
439
|
end
|
317
440
|
|
318
441
|
# Returns +true+ if +self+ is a +belongs_to+ reflection.
|
319
|
-
def belongs_to
|
320
|
-
|
321
|
-
|
442
|
+
def belongs_to?; false; end
|
443
|
+
|
444
|
+
# Returns +true+ if +self+ is a +has_one+ reflection.
|
445
|
+
def has_one?; false; end
|
322
446
|
|
323
447
|
def association_class
|
324
448
|
case macro
|
325
449
|
when :belongs_to
|
326
|
-
if
|
450
|
+
if polymorphic?
|
327
451
|
Associations::BelongsToPolymorphicAssociation
|
328
452
|
else
|
329
453
|
Associations::BelongsToAssociation
|
330
454
|
end
|
331
|
-
when :has_and_belongs_to_many
|
332
|
-
Associations::HasAndBelongsToManyAssociation
|
333
455
|
when :has_many
|
334
456
|
if options[:through]
|
335
457
|
Associations::HasManyThroughAssociation
|
@@ -345,11 +467,100 @@ module ActiveRecord
|
|
345
467
|
end
|
346
468
|
end
|
347
469
|
|
470
|
+
def polymorphic?
|
471
|
+
options[:polymorphic]
|
472
|
+
end
|
473
|
+
|
474
|
+
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
|
475
|
+
INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
|
476
|
+
|
477
|
+
protected
|
478
|
+
|
479
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
480
|
+
self
|
481
|
+
end
|
482
|
+
|
348
483
|
private
|
484
|
+
|
485
|
+
def calculate_constructable(macro, options)
|
486
|
+
case macro
|
487
|
+
when :belongs_to
|
488
|
+
!polymorphic?
|
489
|
+
when :has_one
|
490
|
+
!options[:through]
|
491
|
+
else
|
492
|
+
true
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
# Attempts to find the inverse association name automatically.
|
497
|
+
# If it cannot find a suitable inverse association name, it returns
|
498
|
+
# nil.
|
499
|
+
def inverse_name
|
500
|
+
options.fetch(:inverse_of) do
|
501
|
+
if @automatic_inverse_of == false
|
502
|
+
nil
|
503
|
+
else
|
504
|
+
@automatic_inverse_of ||= automatic_inverse_of
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
# returns either nil or the inverse association name that it finds.
|
510
|
+
def automatic_inverse_of
|
511
|
+
if can_find_inverse_of_automatically?(self)
|
512
|
+
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
|
513
|
+
|
514
|
+
begin
|
515
|
+
reflection = klass._reflect_on_association(inverse_name)
|
516
|
+
rescue NameError
|
517
|
+
# Give up: we couldn't compute the klass type so we won't be able
|
518
|
+
# to find any associations either.
|
519
|
+
reflection = false
|
520
|
+
end
|
521
|
+
|
522
|
+
if valid_inverse_reflection?(reflection)
|
523
|
+
return inverse_name
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
false
|
528
|
+
end
|
529
|
+
|
530
|
+
# Checks if the inverse reflection that is returned from the
|
531
|
+
# +automatic_inverse_of+ method is a valid reflection. We must
|
532
|
+
# make sure that the reflection's active_record name matches up
|
533
|
+
# with the current reflection's klass name.
|
534
|
+
#
|
535
|
+
# Note: klass will always be valid because when there's a NameError
|
536
|
+
# from calling +klass+, +reflection+ will already be set to false.
|
537
|
+
def valid_inverse_reflection?(reflection)
|
538
|
+
reflection &&
|
539
|
+
klass.name == reflection.active_record.name &&
|
540
|
+
can_find_inverse_of_automatically?(reflection)
|
541
|
+
end
|
542
|
+
|
543
|
+
# Checks to see if the reflection doesn't have any options that prevent
|
544
|
+
# us from being able to guess the inverse automatically. First, the
|
545
|
+
# <tt>inverse_of</tt> option cannot be set to false. Second, we must
|
546
|
+
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
|
547
|
+
# Third, we must not have options such as <tt>:polymorphic</tt> or
|
548
|
+
# <tt>:foreign_key</tt> which prevent us from correctly guessing the
|
549
|
+
# inverse association.
|
550
|
+
#
|
551
|
+
# Anything with a scope can additionally ruin our attempt at finding an
|
552
|
+
# inverse, so we exclude reflections with scopes.
|
553
|
+
def can_find_inverse_of_automatically?(reflection)
|
554
|
+
reflection.options[:inverse_of] != false &&
|
555
|
+
VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
|
556
|
+
!INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
|
557
|
+
!reflection.scope
|
558
|
+
end
|
559
|
+
|
349
560
|
def derive_class_name
|
350
|
-
class_name = name.to_s
|
561
|
+
class_name = name.to_s
|
351
562
|
class_name = class_name.singularize if collection?
|
352
|
-
class_name
|
563
|
+
class_name.camelize
|
353
564
|
end
|
354
565
|
|
355
566
|
def derive_foreign_key
|
@@ -362,27 +573,102 @@ module ActiveRecord
|
|
362
573
|
end
|
363
574
|
end
|
364
575
|
|
576
|
+
def derive_join_table
|
577
|
+
ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
|
578
|
+
end
|
579
|
+
|
365
580
|
def primary_key(klass)
|
366
581
|
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
367
582
|
end
|
368
583
|
end
|
369
584
|
|
585
|
+
class HasManyReflection < AssociationReflection # :nodoc:
|
586
|
+
def initialize(name, scope, options, active_record)
|
587
|
+
super(name, scope, options, active_record)
|
588
|
+
end
|
589
|
+
|
590
|
+
def macro; :has_many; end
|
591
|
+
|
592
|
+
def collection?; true; end
|
593
|
+
end
|
594
|
+
|
595
|
+
class HasOneReflection < AssociationReflection # :nodoc:
|
596
|
+
def initialize(name, scope, options, active_record)
|
597
|
+
super(name, scope, options, active_record)
|
598
|
+
end
|
599
|
+
|
600
|
+
def macro; :has_one; end
|
601
|
+
|
602
|
+
def has_one?; true; end
|
603
|
+
end
|
604
|
+
|
605
|
+
class BelongsToReflection < AssociationReflection # :nodoc:
|
606
|
+
def initialize(name, scope, options, active_record)
|
607
|
+
super(name, scope, options, active_record)
|
608
|
+
end
|
609
|
+
|
610
|
+
def macro; :belongs_to; end
|
611
|
+
|
612
|
+
def belongs_to?; true; end
|
613
|
+
|
614
|
+
def join_keys(assoc_klass)
|
615
|
+
key = polymorphic? ? association_primary_key(assoc_klass) : association_primary_key
|
616
|
+
JoinKeys.new(key, foreign_key)
|
617
|
+
end
|
618
|
+
|
619
|
+
def join_id_for(owner) # :nodoc:
|
620
|
+
owner[foreign_key]
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
|
625
|
+
def initialize(name, scope, options, active_record)
|
626
|
+
super
|
627
|
+
end
|
628
|
+
|
629
|
+
def macro; :has_and_belongs_to_many; end
|
630
|
+
|
631
|
+
def collection?
|
632
|
+
true
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
370
636
|
# Holds all the meta-data about a :through association as it was specified
|
371
637
|
# in the Active Record class.
|
372
|
-
class ThroughReflection <
|
638
|
+
class ThroughReflection < AbstractReflection #:nodoc:
|
639
|
+
attr_reader :delegate_reflection
|
373
640
|
delegate :foreign_key, :foreign_type, :association_foreign_key,
|
374
641
|
:active_record_primary_key, :type, :to => :source_reflection
|
375
642
|
|
376
|
-
|
643
|
+
def initialize(delegate_reflection)
|
644
|
+
@delegate_reflection = delegate_reflection
|
645
|
+
@klass = delegate_reflection.options[:anonymous_class]
|
646
|
+
@source_reflection_name = delegate_reflection.options[:source]
|
647
|
+
end
|
648
|
+
|
649
|
+
def klass
|
650
|
+
@klass ||= delegate_reflection.compute_class(class_name)
|
651
|
+
end
|
652
|
+
|
653
|
+
# Returns the source of the through reflection. It checks both a singularized
|
377
654
|
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
|
378
655
|
#
|
379
656
|
# class Post < ActiveRecord::Base
|
380
657
|
# has_many :taggings
|
381
|
-
# has_many :tags, :
|
658
|
+
# has_many :tags, through: :taggings
|
659
|
+
# end
|
660
|
+
#
|
661
|
+
# class Tagging < ActiveRecord::Base
|
662
|
+
# belongs_to :post
|
663
|
+
# belongs_to :tag
|
382
664
|
# end
|
383
665
|
#
|
666
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
667
|
+
# tags_reflection.source_reflection
|
668
|
+
# # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
|
669
|
+
#
|
384
670
|
def source_reflection
|
385
|
-
|
671
|
+
through_reflection.klass._reflect_on_association(source_reflection_name)
|
386
672
|
end
|
387
673
|
|
388
674
|
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
|
@@ -390,14 +676,15 @@ module ActiveRecord
|
|
390
676
|
#
|
391
677
|
# class Post < ActiveRecord::Base
|
392
678
|
# has_many :taggings
|
393
|
-
# has_many :tags, :
|
679
|
+
# has_many :tags, through: :taggings
|
394
680
|
# end
|
395
681
|
#
|
396
682
|
# tags_reflection = Post.reflect_on_association(:tags)
|
397
|
-
#
|
683
|
+
# tags_reflection.through_reflection
|
684
|
+
# # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
|
398
685
|
#
|
399
686
|
def through_reflection
|
400
|
-
|
687
|
+
active_record._reflect_on_association(options[:through])
|
401
688
|
end
|
402
689
|
|
403
690
|
# Returns an array of reflections which are involved in this association. Each item in the
|
@@ -406,9 +693,22 @@ module ActiveRecord
|
|
406
693
|
# The chain is built by recursively calling #chain on the source reflection and the through
|
407
694
|
# reflection. The base case for the recursion is a normal association, which just returns
|
408
695
|
# [self] as its #chain.
|
696
|
+
#
|
697
|
+
# class Post < ActiveRecord::Base
|
698
|
+
# has_many :taggings
|
699
|
+
# has_many :tags, through: :taggings
|
700
|
+
# end
|
701
|
+
#
|
702
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
703
|
+
# tags_reflection.chain
|
704
|
+
# # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
|
705
|
+
# <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
|
706
|
+
#
|
409
707
|
def chain
|
410
708
|
@chain ||= begin
|
411
|
-
|
709
|
+
a = source_reflection.chain
|
710
|
+
b = through_reflection.chain
|
711
|
+
chain = a + b
|
412
712
|
chain[0] = self # Use self so we don't lose the information from :source_type
|
413
713
|
chain
|
414
714
|
end
|
@@ -418,52 +718,60 @@ module ActiveRecord
|
|
418
718
|
#
|
419
719
|
# class Person
|
420
720
|
# has_many :articles
|
421
|
-
# has_many :comment_tags, :
|
721
|
+
# has_many :comment_tags, through: :articles
|
422
722
|
# end
|
423
723
|
#
|
424
724
|
# class Article
|
425
725
|
# has_many :comments
|
426
|
-
# has_many :comment_tags, :
|
726
|
+
# has_many :comment_tags, through: :comments, source: :tags
|
427
727
|
# end
|
428
728
|
#
|
429
729
|
# class Comment
|
430
730
|
# has_many :tags
|
431
731
|
# end
|
432
732
|
#
|
433
|
-
# There may be
|
733
|
+
# There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
|
434
734
|
# but only Comment.tags will be represented in the #chain. So this method creates an array
|
435
|
-
# of
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
@conditions ||= begin
|
440
|
-
conditions = source_reflection.conditions.map { |c| c.dup }
|
735
|
+
# of scopes corresponding to the chain.
|
736
|
+
def scope_chain
|
737
|
+
@scope_chain ||= begin
|
738
|
+
scope_chain = source_reflection.scope_chain.map(&:dup)
|
441
739
|
|
442
|
-
# Add to it the
|
443
|
-
|
740
|
+
# Add to it the scope from this reflection (if any)
|
741
|
+
scope_chain.first << scope if scope
|
444
742
|
|
445
|
-
|
743
|
+
through_scope_chain = through_reflection.scope_chain.map(&:dup)
|
446
744
|
|
447
745
|
if options[:source_type]
|
448
|
-
|
746
|
+
type = foreign_type
|
747
|
+
source_type = options[:source_type]
|
748
|
+
through_scope_chain.first << lambda { |object|
|
749
|
+
where(type => source_type)
|
750
|
+
}
|
449
751
|
end
|
450
752
|
|
451
753
|
# Recursively fill out the rest of the array from the through reflection
|
452
|
-
|
453
|
-
|
454
|
-
# And return
|
455
|
-
conditions
|
754
|
+
scope_chain + through_scope_chain
|
456
755
|
end
|
457
756
|
end
|
458
757
|
|
758
|
+
def join_keys(assoc_klass)
|
759
|
+
source_reflection.join_keys(assoc_klass)
|
760
|
+
end
|
761
|
+
|
459
762
|
# The macro used by the source association
|
460
763
|
def source_macro
|
764
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
765
|
+
ActiveRecord::Base.source_macro is deprecated and will be removed
|
766
|
+
without replacement.
|
767
|
+
MSG
|
768
|
+
|
461
769
|
source_reflection.source_macro
|
462
770
|
end
|
463
771
|
|
464
772
|
# A through association is nested if there would be more than one join table
|
465
773
|
def nested?
|
466
|
-
chain.length > 2
|
774
|
+
chain.length > 2
|
467
775
|
end
|
468
776
|
|
469
777
|
# We want to use the klass from this reflection, rather than just delegate straight to
|
@@ -472,20 +780,45 @@ module ActiveRecord
|
|
472
780
|
def association_primary_key(klass = nil)
|
473
781
|
# Get the "actual" source reflection if the immediate source reflection has a
|
474
782
|
# source reflection itself
|
475
|
-
|
476
|
-
while source_reflection.source_reflection
|
477
|
-
source_reflection = source_reflection.source_reflection
|
478
|
-
end
|
479
|
-
|
480
|
-
source_reflection.options[:primary_key] || primary_key(klass || self.klass)
|
783
|
+
actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
|
481
784
|
end
|
482
785
|
|
483
|
-
# Gets an array of possible <tt>:through</tt> source reflection names
|
786
|
+
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
|
484
787
|
#
|
485
|
-
#
|
788
|
+
# class Post < ActiveRecord::Base
|
789
|
+
# has_many :taggings
|
790
|
+
# has_many :tags, through: :taggings
|
791
|
+
# end
|
792
|
+
#
|
793
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
794
|
+
# tags_reflection.source_reflection_names
|
795
|
+
# # => [:tag, :tags]
|
486
796
|
#
|
487
797
|
def source_reflection_names
|
488
|
-
|
798
|
+
options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
|
799
|
+
end
|
800
|
+
|
801
|
+
def source_reflection_name # :nodoc:
|
802
|
+
return @source_reflection_name if @source_reflection_name
|
803
|
+
|
804
|
+
names = [name.to_s.singularize, name].collect { |n| n.to_sym }.uniq
|
805
|
+
names = names.find_all { |n|
|
806
|
+
through_reflection.klass._reflect_on_association(n)
|
807
|
+
}
|
808
|
+
|
809
|
+
if names.length > 1
|
810
|
+
example_options = options.dup
|
811
|
+
example_options[:source] = source_reflection_names.first
|
812
|
+
ActiveSupport::Deprecation.warn \
|
813
|
+
"Ambiguous source reflection for through association. Please " \
|
814
|
+
"specify a :source directive on your declaration like:\n" \
|
815
|
+
"\n" \
|
816
|
+
" class #{active_record.name} < ActiveRecord::Base\n" \
|
817
|
+
" #{macro} :#{name}, #{example_options}\n" \
|
818
|
+
" end"
|
819
|
+
end
|
820
|
+
|
821
|
+
@source_reflection_name = names.first
|
489
822
|
end
|
490
823
|
|
491
824
|
def source_options
|
@@ -496,39 +829,65 @@ module ActiveRecord
|
|
496
829
|
through_reflection.options
|
497
830
|
end
|
498
831
|
|
832
|
+
def join_id_for(owner) # :nodoc:
|
833
|
+
source_reflection.join_id_for(owner)
|
834
|
+
end
|
835
|
+
|
499
836
|
def check_validity!
|
500
837
|
if through_reflection.nil?
|
501
838
|
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
|
502
839
|
end
|
503
840
|
|
504
|
-
if through_reflection.
|
505
|
-
|
841
|
+
if through_reflection.polymorphic?
|
842
|
+
if has_one?
|
843
|
+
raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
|
844
|
+
else
|
845
|
+
raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
|
846
|
+
end
|
506
847
|
end
|
507
848
|
|
508
849
|
if source_reflection.nil?
|
509
850
|
raise HasManyThroughSourceAssociationNotFoundError.new(self)
|
510
851
|
end
|
511
852
|
|
512
|
-
if options[:source_type] && source_reflection.
|
853
|
+
if options[:source_type] && !source_reflection.polymorphic?
|
513
854
|
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
|
514
855
|
end
|
515
856
|
|
516
|
-
if source_reflection.
|
857
|
+
if source_reflection.polymorphic? && options[:source_type].nil?
|
517
858
|
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
|
518
859
|
end
|
519
860
|
|
520
|
-
if
|
861
|
+
if has_one? && through_reflection.collection?
|
521
862
|
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
|
522
863
|
end
|
523
864
|
|
524
865
|
check_validity_of_inverse!
|
525
866
|
end
|
526
867
|
|
868
|
+
protected
|
869
|
+
|
870
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
871
|
+
source_reflection.send(:actual_source_reflection)
|
872
|
+
end
|
873
|
+
|
874
|
+
def primary_key(klass)
|
875
|
+
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
876
|
+
end
|
877
|
+
|
878
|
+
def inverse_name; delegate_reflection.send(:inverse_name); end
|
879
|
+
|
527
880
|
private
|
528
881
|
def derive_class_name
|
529
882
|
# get the class_name of the belongs_to association of the through reflection
|
530
883
|
options[:source_type] || source_reflection.class_name
|
531
884
|
end
|
885
|
+
|
886
|
+
delegate_methods = AssociationReflection.public_instance_methods -
|
887
|
+
public_instance_methods
|
888
|
+
|
889
|
+
delegate(*delegate_methods, to: :delegate_reflection)
|
890
|
+
|
532
891
|
end
|
533
892
|
end
|
534
893
|
end
|