activerecord 3.1.10 → 4.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +6 -6
- data/CHANGELOG.md +1837 -338
- data/MIT-LICENSE +1 -1
- data/README.rdoc +39 -43
- data/examples/performance.rb +51 -20
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +57 -43
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -39
- data/lib/active_record/associations/association.rb +71 -85
- data/lib/active_record/associations/association_scope.rb +138 -89
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
- data/lib/active_record/associations/builder/association.rb +125 -29
- data/lib/active_record/associations/builder/belongs_to.rb +91 -60
- 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 -52
- data/lib/active_record/associations/builder/singular_association.rb +22 -29
- data/lib/active_record/associations/collection_association.rb +294 -187
- data/lib/active_record/associations/collection_proxy.rb +961 -94
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +118 -23
- data/lib/active_record/associations/has_many_through_association.rb +115 -45
- data/lib/active_record/associations/has_one_association.rb +57 -24
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
- 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 +61 -32
- data/lib/active_record/associations/preloader.rb +113 -87
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +37 -19
- data/lib/active_record/associations.rb +505 -371
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +212 -0
- 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 +141 -51
- data/lib/active_record/attribute_methods/primary_key.rb +87 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +74 -117
- data/lib/active_record/attribute_methods/serialization.rb +70 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
- data/lib/active_record/attribute_methods/write.rb +60 -21
- data/lib/active_record/attribute_methods.rb +409 -48
- 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 +279 -232
- data/lib/active_record/base.rb +84 -1969
- 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 +422 -243
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
- 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 +273 -170
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
- data/lib/active_record/connection_adapters/column.rb +33 -221
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
- data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
- 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 +445 -902
- data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +159 -102
- data/lib/active_record/dynamic_matchers.rb +140 -0
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +102 -34
- data/lib/active_record/explain.rb +38 -0
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +29 -0
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +318 -260
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +247 -0
- data/lib/active_record/integration.rb +113 -0
- 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 +80 -52
- data/lib/active_record/locking/pessimistic.rb +27 -5
- data/lib/active_record/log_subscriber.rb +25 -18
- data/lib/active_record/migration/command_recorder.rb +130 -38
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +532 -201
- data/lib/active_record/model_schema.rb +342 -0
- data/lib/active_record/nested_attributes.rb +229 -139
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +304 -99
- data/lib/active_record/query_cache.rb +25 -43
- data/lib/active_record/querying.rb +68 -0
- data/lib/active_record/railtie.rb +86 -45
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +7 -4
- data/lib/active_record/railties/databases.rake +198 -377
- data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
- data/lib/active_record/readonly_attributes.rb +23 -0
- data/lib/active_record/reflection.rb +516 -165
- data/lib/active_record/relation/batches.rb +96 -45
- data/lib/active_record/relation/calculations.rb +221 -144
- data/lib/active_record/relation/delegation.rb +140 -0
- data/lib/active_record/relation/finder_methods.rb +362 -243
- 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 -41
- data/lib/active_record/relation/query_methods.rb +982 -155
- data/lib/active_record/relation/spawn_methods.rb +50 -110
- data/lib/active_record/relation.rb +371 -180
- data/lib/active_record/result.rb +109 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +191 -0
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +111 -61
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +135 -0
- data/lib/active_record/scoping/named.rb +164 -0
- data/lib/active_record/scoping.rb +87 -0
- data/lib/active_record/serialization.rb +7 -45
- data/lib/active_record/serializers/xml_serializer.rb +14 -65
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +205 -0
- 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 +35 -14
- data/lib/active_record/transactions.rb +141 -74
- data/lib/active_record/translation.rb +22 -0
- 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 +27 -18
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +125 -66
- data/lib/active_record/validations.rb +37 -30
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +80 -25
- data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +132 -53
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
- 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/connection_adapters/abstract/connection_specification.rb +0 -135
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
- data/lib/active_record/dynamic_finder_match.rb +0 -56
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/identity_map.rb +0 -163
- data/lib/active_record/named_scope.rb +0 -200
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/session_store.rb +0 -358
- data/lib/active_record/test_case.rb +0 -69
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
- 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 -16
@@ -1,6 +1,5 @@
|
|
1
|
-
require '
|
2
|
-
require 'active_support/core_ext/
|
3
|
-
require 'active_support/core_ext/object/inclusion'
|
1
|
+
require 'thread'
|
2
|
+
require 'active_support/core_ext/string/filters'
|
4
3
|
|
5
4
|
module ActiveRecord
|
6
5
|
# = Active Record Reflection
|
@@ -8,11 +7,40 @@ module ActiveRecord
|
|
8
7
|
extend ActiveSupport::Concern
|
9
8
|
|
10
9
|
included do
|
11
|
-
class_attribute :
|
12
|
-
|
10
|
+
class_attribute :_reflections, instance_writer: false
|
11
|
+
class_attribute :aggregate_reflections, instance_writer: false
|
12
|
+
self._reflections = {}
|
13
|
+
self.aggregate_reflections = {}
|
13
14
|
end
|
14
15
|
|
15
|
-
|
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
|
16
44
|
# about their associations and aggregations. This information can,
|
17
45
|
# for example, be used in a form builder that takes an Active Record object
|
18
46
|
# and creates input fields for all of the attributes depending on their type
|
@@ -21,22 +49,9 @@ module ActiveRecord
|
|
21
49
|
# MacroReflection class has info for AggregateReflection and AssociationReflection
|
22
50
|
# classes.
|
23
51
|
module ClassMethods
|
24
|
-
def create_reflection(macro, name, options, active_record)
|
25
|
-
case macro
|
26
|
-
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
|
27
|
-
klass = options[:through] ? ThroughReflection : AssociationReflection
|
28
|
-
reflection = klass.new(macro, name, options, active_record)
|
29
|
-
when :composed_of
|
30
|
-
reflection = AggregateReflection.new(macro, name, options, active_record)
|
31
|
-
end
|
32
|
-
|
33
|
-
self.reflections = self.reflections.merge(name => reflection)
|
34
|
-
reflection
|
35
|
-
end
|
36
|
-
|
37
52
|
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
38
53
|
def reflect_on_all_aggregations
|
39
|
-
|
54
|
+
aggregate_reflections.values
|
40
55
|
end
|
41
56
|
|
42
57
|
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
|
@@ -44,7 +59,30 @@ module ActiveRecord
|
|
44
59
|
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
|
45
60
|
#
|
46
61
|
def reflect_on_aggregation(aggregation)
|
47
|
-
|
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
|
48
86
|
end
|
49
87
|
|
50
88
|
# Returns an array of AssociationReflection objects for all the
|
@@ -57,8 +95,9 @@ module ActiveRecord
|
|
57
95
|
# Account.reflect_on_all_associations # returns an array of all associations
|
58
96
|
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
59
97
|
#
|
98
|
+
# @api public
|
60
99
|
def reflect_on_all_associations(macro = nil)
|
61
|
-
association_reflections = reflections.values
|
100
|
+
association_reflections = reflections.values
|
62
101
|
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
63
102
|
end
|
64
103
|
|
@@ -67,65 +106,144 @@ module ActiveRecord
|
|
67
106
|
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
68
107
|
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
69
108
|
#
|
109
|
+
# @api public
|
70
110
|
def reflect_on_association(association)
|
71
|
-
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]
|
72
117
|
end
|
73
118
|
|
74
119
|
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
120
|
+
#
|
121
|
+
# @api public
|
75
122
|
def reflect_on_all_autosave_associations
|
76
123
|
reflections.values.select { |reflection| reflection.options[:autosave] }
|
77
124
|
end
|
125
|
+
|
126
|
+
def clear_reflections_cache #:nodoc:
|
127
|
+
@__reflections = nil
|
128
|
+
end
|
78
129
|
end
|
79
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
|
80
159
|
|
81
|
-
|
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
|
165
|
+
|
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
|
82
190
|
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
83
|
-
|
191
|
+
#
|
192
|
+
# MacroReflection
|
193
|
+
# AssociationReflection
|
194
|
+
# AggregateReflection
|
195
|
+
# HasManyReflection
|
196
|
+
# HasOneReflection
|
197
|
+
# BelongsToReflection
|
198
|
+
# ThroughReflection
|
199
|
+
class MacroReflection < AbstractReflection
|
84
200
|
# Returns the name of the macro.
|
85
201
|
#
|
86
|
-
# <tt>composed_of :balance, :
|
202
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
|
87
203
|
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
|
88
204
|
attr_reader :name
|
89
205
|
|
90
|
-
|
91
|
-
#
|
92
|
-
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
|
93
|
-
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
|
94
|
-
attr_reader :macro
|
206
|
+
attr_reader :scope
|
95
207
|
|
96
208
|
# Returns the hash of options used for the macro.
|
97
209
|
#
|
98
|
-
# <tt>composed_of :balance, :
|
99
|
-
# <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>
|
100
212
|
attr_reader :options
|
101
213
|
|
102
214
|
attr_reader :active_record
|
103
215
|
|
104
216
|
attr_reader :plural_name # :nodoc:
|
105
217
|
|
106
|
-
def initialize(
|
107
|
-
@macro = macro
|
218
|
+
def initialize(name, scope, options, active_record)
|
108
219
|
@name = name
|
220
|
+
@scope = scope
|
109
221
|
@options = options
|
110
222
|
@active_record = active_record
|
223
|
+
@klass = options[:anonymous_class]
|
111
224
|
@plural_name = active_record.pluralize_table_names ?
|
112
225
|
name.to_s.pluralize : name.to_s
|
113
226
|
end
|
114
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
|
+
|
115
237
|
# Returns the class for the macro.
|
116
238
|
#
|
117
|
-
# <tt>composed_of :balance, :
|
239
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
|
118
240
|
# <tt>has_many :clients</tt> returns the Client class
|
119
241
|
def klass
|
120
|
-
@klass ||= class_name
|
242
|
+
@klass ||= compute_class(class_name)
|
121
243
|
end
|
122
244
|
|
123
|
-
|
124
|
-
|
125
|
-
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
|
126
|
-
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
|
127
|
-
def class_name
|
128
|
-
@class_name ||= options[:class_name] || derive_class_name
|
245
|
+
def compute_class(name)
|
246
|
+
name.constantize
|
129
247
|
end
|
130
248
|
|
131
249
|
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
|
@@ -134,14 +252,10 @@ module ActiveRecord
|
|
134
252
|
super ||
|
135
253
|
other_aggregation.kind_of?(self.class) &&
|
136
254
|
name == other_aggregation.name &&
|
137
|
-
other_aggregation.options &&
|
255
|
+
!other_aggregation.options.nil? &&
|
138
256
|
active_record == other_aggregation.active_record
|
139
257
|
end
|
140
258
|
|
141
|
-
def sanitized_conditions #:nodoc:
|
142
|
-
@sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
143
|
-
end
|
144
|
-
|
145
259
|
private
|
146
260
|
def derive_class_name
|
147
261
|
name.to_s.camelize
|
@@ -152,6 +266,10 @@ module ActiveRecord
|
|
152
266
|
# Holds all the meta-data about an aggregation as it was specified in the
|
153
267
|
# Active Record class.
|
154
268
|
class AggregateReflection < MacroReflection #:nodoc:
|
269
|
+
def mapping
|
270
|
+
mapping = options[:mapping] || [name, name]
|
271
|
+
mapping.first.is_a?(Array) ? mapping : [mapping]
|
272
|
+
end
|
155
273
|
end
|
156
274
|
|
157
275
|
# Holds all the meta-data about an association as it was specified in the
|
@@ -170,53 +288,46 @@ module ActiveRecord
|
|
170
288
|
# a new association object. Use +build_association+ or +create_association+
|
171
289
|
# instead. This allows plugins to hook into association object creation.
|
172
290
|
def klass
|
173
|
-
@klass ||=
|
291
|
+
@klass ||= compute_class(class_name)
|
174
292
|
end
|
175
293
|
|
176
|
-
def
|
177
|
-
|
178
|
-
@collection = macro.in?([:has_many, :has_and_belongs_to_many])
|
294
|
+
def compute_class(name)
|
295
|
+
active_record.send(:compute_type, name)
|
179
296
|
end
|
180
297
|
|
181
|
-
|
182
|
-
|
183
|
-
# see Association#build_record for details.
|
184
|
-
attr_accessor :original_build_association_called # :nodoc
|
298
|
+
attr_reader :type, :foreign_type
|
299
|
+
attr_accessor :parent_reflection # [:name, Reflection]
|
185
300
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
@
|
190
|
-
|
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
|
+
}
|
191
319
|
end
|
192
320
|
|
193
|
-
def
|
194
|
-
@
|
321
|
+
def constructable? # :nodoc:
|
322
|
+
@constructable
|
195
323
|
end
|
196
324
|
|
197
|
-
def
|
198
|
-
@
|
325
|
+
def join_table
|
326
|
+
@join_table ||= options[:join_table] || derive_join_table
|
199
327
|
end
|
200
328
|
|
201
329
|
def foreign_key
|
202
|
-
@foreign_key ||= options[:foreign_key] || derive_foreign_key
|
203
|
-
end
|
204
|
-
|
205
|
-
def primary_key_name
|
206
|
-
foreign_key
|
207
|
-
end
|
208
|
-
deprecate :primary_key_name => :foreign_key
|
209
|
-
|
210
|
-
def foreign_type
|
211
|
-
@foreign_type ||= options[:foreign_type] || "#{name}_type"
|
212
|
-
end
|
213
|
-
|
214
|
-
def type
|
215
|
-
@type ||= options[:as] && "#{options[:as]}_type"
|
216
|
-
end
|
217
|
-
|
218
|
-
def primary_key_column
|
219
|
-
@primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
|
330
|
+
@foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
|
220
331
|
end
|
221
332
|
|
222
333
|
def association_foreign_key
|
@@ -240,32 +351,36 @@ module ActiveRecord
|
|
240
351
|
end
|
241
352
|
end
|
242
353
|
|
243
|
-
def columns(tbl_name, log_msg)
|
244
|
-
@columns ||= klass.connection.columns(tbl_name, log_msg)
|
245
|
-
end
|
246
|
-
|
247
|
-
def reset_column_information
|
248
|
-
@columns = nil
|
249
|
-
end
|
250
|
-
|
251
354
|
def check_validity!
|
252
355
|
check_validity_of_inverse!
|
253
356
|
end
|
254
357
|
|
255
|
-
def
|
256
|
-
unless
|
257
|
-
|
258
|
-
|
259
|
-
|
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
|
260
370
|
end
|
261
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
|
262
377
|
|
263
378
|
def through_reflection
|
264
379
|
nil
|
265
380
|
end
|
266
381
|
|
267
382
|
def source_reflection
|
268
|
-
|
383
|
+
self
|
269
384
|
end
|
270
385
|
|
271
386
|
# A chain of reflections from this one back to the owner. For more see the explanation in
|
@@ -274,28 +389,23 @@ module ActiveRecord
|
|
274
389
|
[self]
|
275
390
|
end
|
276
391
|
|
277
|
-
|
278
|
-
|
279
|
-
# a hash, array, arel predicate, etc...)
|
280
|
-
def conditions
|
281
|
-
[[options[:conditions]].compact]
|
392
|
+
def nested?
|
393
|
+
false
|
282
394
|
end
|
283
395
|
|
284
|
-
|
285
|
-
|
286
|
-
def
|
287
|
-
|
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]] : [[]]
|
288
400
|
end
|
289
401
|
|
290
|
-
def
|
291
|
-
|
292
|
-
@inverse_of ||= klass.reflect_on_association(options[:inverse_of])
|
293
|
-
end
|
402
|
+
def has_inverse?
|
403
|
+
inverse_name
|
294
404
|
end
|
295
405
|
|
296
406
|
def polymorphic_inverse_of(associated_class)
|
297
407
|
if has_inverse?
|
298
|
-
if inverse_relationship = associated_class.
|
408
|
+
if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
|
299
409
|
inverse_relationship
|
300
410
|
else
|
301
411
|
raise InverseOfAssociationNotFoundError.new(self, associated_class)
|
@@ -303,41 +413,45 @@ module ActiveRecord
|
|
303
413
|
end
|
304
414
|
end
|
305
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
|
+
|
306
421
|
# Returns whether or not this association reflection is for a collection
|
307
422
|
# association. Returns +true+ if the +macro+ is either +has_many+ or
|
308
423
|
# +has_and_belongs_to_many+, +false+ otherwise.
|
309
424
|
def collection?
|
310
|
-
|
425
|
+
false
|
311
426
|
end
|
312
427
|
|
313
428
|
# Returns whether or not the association should be validated as part of
|
314
429
|
# the parent's validation.
|
315
430
|
#
|
316
431
|
# Unless you explicitly disable validation with
|
317
|
-
# <tt
|
432
|
+
# <tt>validate: false</tt>, validation will take place when:
|
318
433
|
#
|
319
|
-
# * you explicitly enable validation; <tt
|
320
|
-
# * you use autosave; <tt
|
434
|
+
# * you explicitly enable validation; <tt>validate: true</tt>
|
435
|
+
# * you use autosave; <tt>autosave: true</tt>
|
321
436
|
# * the association is a +has_many+ association
|
322
437
|
def validate?
|
323
|
-
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true ||
|
438
|
+
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
|
324
439
|
end
|
325
440
|
|
326
441
|
# Returns +true+ if +self+ is a +belongs_to+ reflection.
|
327
|
-
def belongs_to
|
328
|
-
|
329
|
-
|
442
|
+
def belongs_to?; false; end
|
443
|
+
|
444
|
+
# Returns +true+ if +self+ is a +has_one+ reflection.
|
445
|
+
def has_one?; false; end
|
330
446
|
|
331
447
|
def association_class
|
332
448
|
case macro
|
333
449
|
when :belongs_to
|
334
|
-
if
|
450
|
+
if polymorphic?
|
335
451
|
Associations::BelongsToPolymorphicAssociation
|
336
452
|
else
|
337
453
|
Associations::BelongsToAssociation
|
338
454
|
end
|
339
|
-
when :has_and_belongs_to_many
|
340
|
-
Associations::HasAndBelongsToManyAssociation
|
341
455
|
when :has_many
|
342
456
|
if options[:through]
|
343
457
|
Associations::HasManyThroughAssociation
|
@@ -353,11 +467,100 @@ module ActiveRecord
|
|
353
467
|
end
|
354
468
|
end
|
355
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
|
+
|
356
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
|
+
|
357
560
|
def derive_class_name
|
358
|
-
class_name = name.to_s
|
561
|
+
class_name = name.to_s
|
359
562
|
class_name = class_name.singularize if collection?
|
360
|
-
class_name
|
563
|
+
class_name.camelize
|
361
564
|
end
|
362
565
|
|
363
566
|
def derive_foreign_key
|
@@ -370,27 +573,102 @@ module ActiveRecord
|
|
370
573
|
end
|
371
574
|
end
|
372
575
|
|
576
|
+
def derive_join_table
|
577
|
+
ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
|
578
|
+
end
|
579
|
+
|
373
580
|
def primary_key(klass)
|
374
581
|
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
375
582
|
end
|
376
583
|
end
|
377
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
|
+
|
378
636
|
# Holds all the meta-data about a :through association as it was specified
|
379
637
|
# in the Active Record class.
|
380
|
-
class ThroughReflection <
|
638
|
+
class ThroughReflection < AbstractReflection #:nodoc:
|
639
|
+
attr_reader :delegate_reflection
|
381
640
|
delegate :foreign_key, :foreign_type, :association_foreign_key,
|
382
641
|
:active_record_primary_key, :type, :to => :source_reflection
|
383
642
|
|
384
|
-
|
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
|
385
654
|
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
|
386
655
|
#
|
387
656
|
# class Post < ActiveRecord::Base
|
388
657
|
# has_many :taggings
|
389
|
-
# 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
|
390
664
|
# end
|
391
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
|
+
#
|
392
670
|
def source_reflection
|
393
|
-
|
671
|
+
through_reflection.klass._reflect_on_association(source_reflection_name)
|
394
672
|
end
|
395
673
|
|
396
674
|
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
|
@@ -398,14 +676,15 @@ module ActiveRecord
|
|
398
676
|
#
|
399
677
|
# class Post < ActiveRecord::Base
|
400
678
|
# has_many :taggings
|
401
|
-
# has_many :tags, :
|
679
|
+
# has_many :tags, through: :taggings
|
402
680
|
# end
|
403
681
|
#
|
404
682
|
# tags_reflection = Post.reflect_on_association(:tags)
|
405
|
-
#
|
683
|
+
# tags_reflection.through_reflection
|
684
|
+
# # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
|
406
685
|
#
|
407
686
|
def through_reflection
|
408
|
-
|
687
|
+
active_record._reflect_on_association(options[:through])
|
409
688
|
end
|
410
689
|
|
411
690
|
# Returns an array of reflections which are involved in this association. Each item in the
|
@@ -414,9 +693,22 @@ module ActiveRecord
|
|
414
693
|
# The chain is built by recursively calling #chain on the source reflection and the through
|
415
694
|
# reflection. The base case for the recursion is a normal association, which just returns
|
416
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
|
+
#
|
417
707
|
def chain
|
418
708
|
@chain ||= begin
|
419
|
-
|
709
|
+
a = source_reflection.chain
|
710
|
+
b = through_reflection.chain
|
711
|
+
chain = a + b
|
420
712
|
chain[0] = self # Use self so we don't lose the information from :source_type
|
421
713
|
chain
|
422
714
|
end
|
@@ -426,52 +718,60 @@ module ActiveRecord
|
|
426
718
|
#
|
427
719
|
# class Person
|
428
720
|
# has_many :articles
|
429
|
-
# has_many :comment_tags, :
|
721
|
+
# has_many :comment_tags, through: :articles
|
430
722
|
# end
|
431
723
|
#
|
432
724
|
# class Article
|
433
725
|
# has_many :comments
|
434
|
-
# has_many :comment_tags, :
|
726
|
+
# has_many :comment_tags, through: :comments, source: :tags
|
435
727
|
# end
|
436
728
|
#
|
437
729
|
# class Comment
|
438
730
|
# has_many :tags
|
439
731
|
# end
|
440
732
|
#
|
441
|
-
# There may be
|
733
|
+
# There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
|
442
734
|
# but only Comment.tags will be represented in the #chain. So this method creates an array
|
443
|
-
# of
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
@conditions ||= begin
|
448
|
-
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)
|
449
739
|
|
450
|
-
# Add to it the
|
451
|
-
|
740
|
+
# Add to it the scope from this reflection (if any)
|
741
|
+
scope_chain.first << scope if scope
|
452
742
|
|
453
|
-
|
743
|
+
through_scope_chain = through_reflection.scope_chain.map(&:dup)
|
454
744
|
|
455
745
|
if options[:source_type]
|
456
|
-
|
746
|
+
type = foreign_type
|
747
|
+
source_type = options[:source_type]
|
748
|
+
through_scope_chain.first << lambda { |object|
|
749
|
+
where(type => source_type)
|
750
|
+
}
|
457
751
|
end
|
458
752
|
|
459
753
|
# Recursively fill out the rest of the array from the through reflection
|
460
|
-
|
461
|
-
|
462
|
-
# And return
|
463
|
-
conditions
|
754
|
+
scope_chain + through_scope_chain
|
464
755
|
end
|
465
756
|
end
|
466
757
|
|
758
|
+
def join_keys(assoc_klass)
|
759
|
+
source_reflection.join_keys(assoc_klass)
|
760
|
+
end
|
761
|
+
|
467
762
|
# The macro used by the source association
|
468
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
|
+
|
469
769
|
source_reflection.source_macro
|
470
770
|
end
|
471
771
|
|
472
|
-
# A through association is nested
|
772
|
+
# A through association is nested if there would be more than one join table
|
473
773
|
def nested?
|
474
|
-
chain.length > 2
|
774
|
+
chain.length > 2
|
475
775
|
end
|
476
776
|
|
477
777
|
# We want to use the klass from this reflection, rather than just delegate straight to
|
@@ -480,20 +780,45 @@ module ActiveRecord
|
|
480
780
|
def association_primary_key(klass = nil)
|
481
781
|
# Get the "actual" source reflection if the immediate source reflection has a
|
482
782
|
# source reflection itself
|
483
|
-
|
484
|
-
while source_reflection.source_reflection
|
485
|
-
source_reflection = source_reflection.source_reflection
|
486
|
-
end
|
487
|
-
|
488
|
-
source_reflection.options[:primary_key] || primary_key(klass || self.klass)
|
783
|
+
actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
|
489
784
|
end
|
490
785
|
|
491
|
-
# 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.
|
492
787
|
#
|
493
|
-
#
|
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]
|
494
796
|
#
|
495
797
|
def source_reflection_names
|
496
|
-
|
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
|
497
822
|
end
|
498
823
|
|
499
824
|
def source_options
|
@@ -504,39 +829,65 @@ module ActiveRecord
|
|
504
829
|
through_reflection.options
|
505
830
|
end
|
506
831
|
|
832
|
+
def join_id_for(owner) # :nodoc:
|
833
|
+
source_reflection.join_id_for(owner)
|
834
|
+
end
|
835
|
+
|
507
836
|
def check_validity!
|
508
837
|
if through_reflection.nil?
|
509
838
|
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
|
510
839
|
end
|
511
840
|
|
512
|
-
if through_reflection.
|
513
|
-
|
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
|
514
847
|
end
|
515
848
|
|
516
849
|
if source_reflection.nil?
|
517
850
|
raise HasManyThroughSourceAssociationNotFoundError.new(self)
|
518
851
|
end
|
519
852
|
|
520
|
-
if options[:source_type] && source_reflection.
|
853
|
+
if options[:source_type] && !source_reflection.polymorphic?
|
521
854
|
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
|
522
855
|
end
|
523
856
|
|
524
|
-
if source_reflection.
|
857
|
+
if source_reflection.polymorphic? && options[:source_type].nil?
|
525
858
|
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
|
526
859
|
end
|
527
860
|
|
528
|
-
if
|
861
|
+
if has_one? && through_reflection.collection?
|
529
862
|
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
|
530
863
|
end
|
531
864
|
|
532
865
|
check_validity_of_inverse!
|
533
866
|
end
|
534
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
|
+
|
535
880
|
private
|
536
881
|
def derive_class_name
|
537
882
|
# get the class_name of the belongs_to association of the through reflection
|
538
883
|
options[:source_type] || source_reflection.class_name
|
539
884
|
end
|
885
|
+
|
886
|
+
delegate_methods = AssociationReflection.public_instance_methods -
|
887
|
+
public_instance_methods
|
888
|
+
|
889
|
+
delegate(*delegate_methods, to: :delegate_reflection)
|
890
|
+
|
540
891
|
end
|
541
892
|
end
|
542
893
|
end
|