activerecord 3.2.19 → 5.0.0
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 +7 -0
- data/CHANGELOG.md +1715 -604
- data/MIT-LICENSE +2 -2
- data/README.rdoc +40 -45
- data/examples/performance.rb +33 -22
- data/examples/simple.rb +3 -4
- data/lib/active_record/aggregations.rb +76 -51
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +54 -40
- data/lib/active_record/associations/association.rb +76 -56
- data/lib/active_record/associations/association_scope.rb +125 -93
- data/lib/active_record/associations/belongs_to_association.rb +57 -28
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +120 -32
- data/lib/active_record/associations/builder/belongs_to.rb +115 -62
- data/lib/active_record/associations/builder/collection_association.rb +61 -53
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
- data/lib/active_record/associations/builder/has_many.rb +9 -65
- data/lib/active_record/associations/builder/has_one.rb +18 -52
- data/lib/active_record/associations/builder/singular_association.rb +18 -19
- data/lib/active_record/associations/collection_association.rb +268 -186
- data/lib/active_record/associations/collection_proxy.rb +1003 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +81 -41
- data/lib/active_record/associations/has_many_through_association.rb +76 -55
- data/lib/active_record/associations/has_one_association.rb +51 -21
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
- 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 +239 -155
- data/lib/active_record/associations/preloader/association.rb +97 -62
- data/lib/active_record/associations/preloader/collection_association.rb +2 -8
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +75 -33
- data/lib/active_record/associations/preloader.rb +111 -79
- data/lib/active_record/associations/singular_association.rb +35 -13
- data/lib/active_record/associations/through_association.rb +41 -19
- data/lib/active_record/associations.rb +727 -501
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +213 -0
- data/lib/active_record/attribute_assignment.rb +32 -162
- data/lib/active_record/attribute_decorators.rb +67 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -61
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +7 -6
- data/lib/active_record/attribute_methods/read.rb +56 -117
- data/lib/active_record/attribute_methods/serialization.rb +43 -96
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
- data/lib/active_record/attribute_methods/write.rb +34 -45
- data/lib/active_record/attribute_methods.rb +333 -144
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +108 -0
- data/lib/active_record/attribute_set.rb +108 -0
- data/lib/active_record/attributes.rb +265 -0
- data/lib/active_record/autosave_association.rb +285 -223
- data/lib/active_record/base.rb +95 -490
- data/lib/active_record/callbacks.rb +95 -61
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +28 -19
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
- data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
- data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
- data/lib/active_record/connection_adapters/column.rb +30 -259
- data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
- data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -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 +48 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -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/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -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 +31 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
- data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +155 -0
- data/lib/active_record/core.rb +561 -0
- data/lib/active_record/counter_cache.rb +146 -105
- data/lib/active_record/dynamic_matchers.rb +101 -64
- data/lib/active_record/enum.rb +234 -0
- data/lib/active_record/errors.rb +153 -56
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +10 -6
- data/lib/active_record/fixture_set/file.rb +77 -0
- data/lib/active_record/fixtures.rb +355 -232
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +144 -79
- data/lib/active_record/integration.rb +66 -13
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +46 -0
- data/lib/active_record/locale/en.yml +9 -1
- data/lib/active_record/locking/optimistic.rb +77 -56
- data/lib/active_record/locking/pessimistic.rb +6 -6
- data/lib/active_record/log_subscriber.rb +53 -28
- data/lib/active_record/migration/command_recorder.rb +166 -33
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +792 -264
- data/lib/active_record/model_schema.rb +192 -130
- data/lib/active_record/nested_attributes.rb +238 -145
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +89 -0
- data/lib/active_record/persistence.rb +357 -157
- data/lib/active_record/query_cache.rb +22 -43
- data/lib/active_record/querying.rb +34 -23
- data/lib/active_record/railtie.rb +88 -48
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +5 -4
- data/lib/active_record/railties/databases.rake +170 -422
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -5
- data/lib/active_record/reflection.rb +715 -189
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +203 -50
- data/lib/active_record/relation/calculations.rb +203 -194
- data/lib/active_record/relation/delegation.rb +103 -25
- data/lib/active_record/relation/finder_methods.rb +457 -261
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +167 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +153 -48
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +1019 -194
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +46 -150
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/relation.rb +450 -245
- data/lib/active_record/result.rb +104 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +120 -94
- data/lib/active_record/schema.rb +28 -18
- data/lib/active_record/schema_dumper.rb +141 -74
- data/lib/active_record/schema_migration.rb +50 -0
- data/lib/active_record/scoping/default.rb +64 -57
- data/lib/active_record/scoping/named.rb +93 -108
- data/lib/active_record/scoping.rb +73 -121
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +7 -5
- data/lib/active_record/statement_cache.rb +113 -0
- data/lib/active_record/store.rb +173 -15
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +313 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
- data/lib/active_record/timestamp.rb +42 -24
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +233 -105
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +7 -0
- data/lib/active_record/type/date_time.rb +7 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/internal/abstract_json.rb +29 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +63 -0
- data/lib/active_record/type/time.rb +20 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type.rb +72 -0
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +33 -18
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +66 -0
- data/lib/active_record/validations/uniqueness.rb +128 -68
- data/lib/active_record/validations.rb +48 -40
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +71 -47
- data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
- data/lib/rails/generators/active_record/migration.rb +18 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +188 -134
- 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/mysql_adapter.rb +0 -441
- 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/serializers/xml_serializer.rb +0 -203
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,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,35 +7,51 @@ 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
|
-
|
15
|
-
|
16
|
-
|
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 the ability to examine the associations and aggregations of
|
44
|
+
# Active Record classes and objects. This information, for example,
|
45
|
+
# can 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
|
18
47
|
# and displays the associations to other objects.
|
19
48
|
#
|
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 an AssociationReflection as the value.
|
66
|
+
#
|
67
|
+
# Account.reflections # => {"balance" => AggregateReflection}
|
68
|
+
#
|
69
|
+
def reflections
|
70
|
+
@__reflections ||= begin
|
71
|
+
ref = {}
|
72
|
+
|
73
|
+
_reflections.each do |name, reflection|
|
74
|
+
parent_reflection = reflection.parent_reflection
|
75
|
+
|
76
|
+
if parent_reflection
|
77
|
+
parent_name = parent_reflection.name
|
78
|
+
ref[parent_name.to_s] = 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
|
@@ -57,8 +96,9 @@ module ActiveRecord
|
|
57
96
|
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
58
97
|
#
|
59
98
|
def reflect_on_all_associations(macro = nil)
|
60
|
-
association_reflections = reflections.values
|
61
|
-
|
99
|
+
association_reflections = reflections.values
|
100
|
+
association_reflections.select! { |reflection| reflection.macro == macro } if macro
|
101
|
+
association_reflections
|
62
102
|
end
|
63
103
|
|
64
104
|
# Returns the AssociationReflection object for the +association+ (use the symbol).
|
@@ -67,64 +107,198 @@ module ActiveRecord
|
|
67
107
|
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
68
108
|
#
|
69
109
|
def reflect_on_association(association)
|
70
|
-
reflections[association
|
110
|
+
reflections[association.to_s]
|
111
|
+
end
|
112
|
+
|
113
|
+
def _reflect_on_association(association) #:nodoc:
|
114
|
+
_reflections[association.to_s]
|
71
115
|
end
|
72
116
|
|
73
117
|
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
74
118
|
def reflect_on_all_autosave_associations
|
75
119
|
reflections.values.select { |reflection| reflection.options[:autosave] }
|
76
120
|
end
|
121
|
+
|
122
|
+
def clear_reflections_cache # :nodoc:
|
123
|
+
@__reflections = nil
|
124
|
+
end
|
77
125
|
end
|
78
126
|
|
127
|
+
# Holds all the methods that are shared between MacroReflection and ThroughReflection.
|
128
|
+
#
|
129
|
+
# AbstractReflection
|
130
|
+
# MacroReflection
|
131
|
+
# AggregateReflection
|
132
|
+
# AssociationReflection
|
133
|
+
# HasManyReflection
|
134
|
+
# HasOneReflection
|
135
|
+
# BelongsToReflection
|
136
|
+
# HasAndBelongsToManyReflection
|
137
|
+
# ThroughReflection
|
138
|
+
# PolymorphicReflection
|
139
|
+
# RuntimeReflection
|
140
|
+
class AbstractReflection # :nodoc:
|
141
|
+
def through_reflection?
|
142
|
+
false
|
143
|
+
end
|
144
|
+
|
145
|
+
def table_name
|
146
|
+
klass.table_name
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns a new, unsaved instance of the associated class. +attributes+ will
|
150
|
+
# be passed to the class's constructor.
|
151
|
+
def build_association(attributes, &block)
|
152
|
+
klass.new(attributes, &block)
|
153
|
+
end
|
154
|
+
|
155
|
+
def quoted_table_name
|
156
|
+
klass.quoted_table_name
|
157
|
+
end
|
158
|
+
|
159
|
+
def primary_key_type
|
160
|
+
klass.type_for_attribute(klass.primary_key)
|
161
|
+
end
|
79
162
|
|
80
|
-
|
163
|
+
# Returns the class name for the macro.
|
164
|
+
#
|
165
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>'Money'</tt>
|
166
|
+
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
|
167
|
+
def class_name
|
168
|
+
@class_name ||= (options[:class_name] || derive_class_name).to_s
|
169
|
+
end
|
170
|
+
|
171
|
+
JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
|
172
|
+
|
173
|
+
def join_keys(association_klass)
|
174
|
+
JoinKeys.new(foreign_key, active_record_primary_key)
|
175
|
+
end
|
176
|
+
|
177
|
+
def constraints
|
178
|
+
scope_chain.flatten
|
179
|
+
end
|
180
|
+
|
181
|
+
def counter_cache_column
|
182
|
+
if belongs_to?
|
183
|
+
if options[:counter_cache] == true
|
184
|
+
"#{active_record.name.demodulize.underscore.pluralize}_count"
|
185
|
+
elsif options[:counter_cache]
|
186
|
+
options[:counter_cache].to_s
|
187
|
+
end
|
188
|
+
else
|
189
|
+
options[:counter_cache] ? options[:counter_cache].to_s : "#{name}_count"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def inverse_of
|
194
|
+
return unless inverse_name
|
195
|
+
|
196
|
+
@inverse_of ||= klass._reflect_on_association inverse_name
|
197
|
+
end
|
198
|
+
|
199
|
+
def check_validity_of_inverse!
|
200
|
+
unless polymorphic?
|
201
|
+
if has_inverse? && inverse_of.nil?
|
202
|
+
raise InverseOfAssociationNotFoundError.new(self)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# This shit is nasty. We need to avoid the following situation:
|
208
|
+
#
|
209
|
+
# * An associated record is deleted via record.destroy
|
210
|
+
# * Hence the callbacks run, and they find a belongs_to on the record with a
|
211
|
+
# :counter_cache options which points back at our owner. So they update the
|
212
|
+
# counter cache.
|
213
|
+
# * In which case, we must make sure to *not* update the counter cache, or else
|
214
|
+
# it will be decremented twice.
|
215
|
+
#
|
216
|
+
# Hence this method.
|
217
|
+
def inverse_which_updates_counter_cache
|
218
|
+
return @inverse_which_updates_counter_cache if defined?(@inverse_which_updates_counter_cache)
|
219
|
+
@inverse_which_updates_counter_cache = klass.reflect_on_all_associations(:belongs_to).find do |inverse|
|
220
|
+
inverse.counter_cache_column == counter_cache_column
|
221
|
+
end
|
222
|
+
end
|
223
|
+
alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
|
224
|
+
|
225
|
+
def inverse_updates_counter_in_memory?
|
226
|
+
inverse_of && inverse_which_updates_counter_cache == inverse_of
|
227
|
+
end
|
228
|
+
|
229
|
+
# Returns whether a counter cache should be used for this association.
|
230
|
+
#
|
231
|
+
# The counter_cache option must be given on either the owner or inverse
|
232
|
+
# association, and the column must be present on the owner.
|
233
|
+
def has_cached_counter?
|
234
|
+
options[:counter_cache] ||
|
235
|
+
inverse_which_updates_counter_cache && inverse_which_updates_counter_cache.options[:counter_cache] &&
|
236
|
+
!!active_record.columns_hash[counter_cache_column]
|
237
|
+
end
|
238
|
+
|
239
|
+
def counter_must_be_updated_by_has_many?
|
240
|
+
!inverse_updates_counter_in_memory? && has_cached_counter?
|
241
|
+
end
|
242
|
+
|
243
|
+
def alias_candidate(name)
|
244
|
+
"#{plural_name}_#{name}"
|
245
|
+
end
|
246
|
+
|
247
|
+
def chain
|
248
|
+
collect_join_chain
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Base class for AggregateReflection and AssociationReflection. Objects of
|
81
253
|
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
82
|
-
class MacroReflection
|
254
|
+
class MacroReflection < AbstractReflection
|
83
255
|
# Returns the name of the macro.
|
84
256
|
#
|
85
|
-
# <tt>composed_of :balance, :
|
257
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>:balance</tt>
|
86
258
|
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
|
87
259
|
attr_reader :name
|
88
260
|
|
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
|
261
|
+
attr_reader :scope
|
94
262
|
|
95
263
|
# Returns the hash of options used for the macro.
|
96
264
|
#
|
97
|
-
# <tt>composed_of :balance, :
|
98
|
-
# <tt>has_many :clients</tt> returns
|
265
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns <tt>{ class_name: "Money" }</tt>
|
266
|
+
# <tt>has_many :clients</tt> returns <tt>{}</tt>
|
99
267
|
attr_reader :options
|
100
268
|
|
101
269
|
attr_reader :active_record
|
102
270
|
|
103
271
|
attr_reader :plural_name # :nodoc:
|
104
272
|
|
105
|
-
def initialize(
|
106
|
-
@macro = macro
|
273
|
+
def initialize(name, scope, options, active_record)
|
107
274
|
@name = name
|
275
|
+
@scope = scope
|
108
276
|
@options = options
|
109
277
|
@active_record = active_record
|
278
|
+
@klass = options[:anonymous_class]
|
110
279
|
@plural_name = active_record.pluralize_table_names ?
|
111
280
|
name.to_s.pluralize : name.to_s
|
112
281
|
end
|
113
282
|
|
283
|
+
def autosave=(autosave)
|
284
|
+
@automatic_inverse_of = false
|
285
|
+
@options[:autosave] = autosave
|
286
|
+
parent_reflection = self.parent_reflection
|
287
|
+
if parent_reflection
|
288
|
+
parent_reflection.autosave = autosave
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
114
292
|
# Returns the class for the macro.
|
115
293
|
#
|
116
|
-
# <tt>composed_of :balance, :
|
294
|
+
# <tt>composed_of :balance, class_name: 'Money'</tt> returns the Money class
|
117
295
|
# <tt>has_many :clients</tt> returns the Client class
|
118
296
|
def klass
|
119
|
-
@klass ||= class_name
|
297
|
+
@klass ||= compute_class(class_name)
|
120
298
|
end
|
121
299
|
|
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
|
300
|
+
def compute_class(name)
|
301
|
+
name.constantize
|
128
302
|
end
|
129
303
|
|
130
304
|
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
|
@@ -133,14 +307,10 @@ module ActiveRecord
|
|
133
307
|
super ||
|
134
308
|
other_aggregation.kind_of?(self.class) &&
|
135
309
|
name == other_aggregation.name &&
|
136
|
-
other_aggregation.options &&
|
310
|
+
!other_aggregation.options.nil? &&
|
137
311
|
active_record == other_aggregation.active_record
|
138
312
|
end
|
139
313
|
|
140
|
-
def sanitized_conditions #:nodoc:
|
141
|
-
@sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
142
|
-
end
|
143
|
-
|
144
314
|
private
|
145
315
|
def derive_class_name
|
146
316
|
name.to_s.camelize
|
@@ -151,6 +321,10 @@ module ActiveRecord
|
|
151
321
|
# Holds all the meta-data about an aggregation as it was specified in the
|
152
322
|
# Active Record class.
|
153
323
|
class AggregateReflection < MacroReflection #:nodoc:
|
324
|
+
def mapping
|
325
|
+
mapping = options[:mapping] || [name, name]
|
326
|
+
mapping.first.is_a?(Array) ? mapping : [mapping]
|
327
|
+
end
|
154
328
|
end
|
155
329
|
|
156
330
|
# Holds all the meta-data about an association as it was specified in the
|
@@ -169,42 +343,46 @@ module ActiveRecord
|
|
169
343
|
# a new association object. Use +build_association+ or +create_association+
|
170
344
|
# instead. This allows plugins to hook into association object creation.
|
171
345
|
def klass
|
172
|
-
@klass ||=
|
346
|
+
@klass ||= compute_class(class_name)
|
173
347
|
end
|
174
348
|
|
175
|
-
def
|
176
|
-
|
177
|
-
@collection = macro.in?([:has_many, :has_and_belongs_to_many])
|
349
|
+
def compute_class(name)
|
350
|
+
active_record.send(:compute_type, name)
|
178
351
|
end
|
179
352
|
|
180
|
-
|
181
|
-
|
182
|
-
def build_association(*options, &block)
|
183
|
-
klass.new(*options, &block)
|
184
|
-
end
|
353
|
+
attr_reader :type, :foreign_type
|
354
|
+
attr_accessor :parent_reflection # Reflection
|
185
355
|
|
186
|
-
def
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
@
|
356
|
+
def initialize(name, scope, options, active_record)
|
357
|
+
super
|
358
|
+
@automatic_inverse_of = nil
|
359
|
+
@type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
|
360
|
+
@foreign_type = options[:foreign_type] || "#{name}_type"
|
361
|
+
@constructable = calculate_constructable(macro, options)
|
362
|
+
@association_scope_cache = {}
|
363
|
+
@scope_lock = Mutex.new
|
192
364
|
end
|
193
365
|
|
194
|
-
def
|
195
|
-
|
366
|
+
def association_scope_cache(conn, owner)
|
367
|
+
key = conn.prepared_statements
|
368
|
+
if polymorphic?
|
369
|
+
key = [key, owner._read_attribute(@foreign_type)]
|
370
|
+
end
|
371
|
+
@association_scope_cache[key] ||= @scope_lock.synchronize {
|
372
|
+
@association_scope_cache[key] ||= yield
|
373
|
+
}
|
196
374
|
end
|
197
375
|
|
198
|
-
def
|
199
|
-
@
|
376
|
+
def constructable? # :nodoc:
|
377
|
+
@constructable
|
200
378
|
end
|
201
379
|
|
202
|
-
def
|
203
|
-
@
|
380
|
+
def join_table
|
381
|
+
@join_table ||= options[:join_table] || derive_join_table
|
204
382
|
end
|
205
383
|
|
206
|
-
def
|
207
|
-
@
|
384
|
+
def foreign_key
|
385
|
+
@foreign_key ||= options[:foreign_key] || derive_foreign_key.freeze
|
208
386
|
end
|
209
387
|
|
210
388
|
def association_foreign_key
|
@@ -220,74 +398,68 @@ module ActiveRecord
|
|
220
398
|
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
|
221
399
|
end
|
222
400
|
|
223
|
-
def counter_cache_column
|
224
|
-
if options[:counter_cache] == true
|
225
|
-
"#{active_record.name.demodulize.underscore.pluralize}_count"
|
226
|
-
elsif options[:counter_cache]
|
227
|
-
options[:counter_cache].to_s
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
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
401
|
def check_validity!
|
240
402
|
check_validity_of_inverse!
|
241
403
|
end
|
242
404
|
|
243
|
-
def
|
244
|
-
unless
|
245
|
-
|
246
|
-
|
247
|
-
|
405
|
+
def check_preloadable!
|
406
|
+
return unless scope
|
407
|
+
|
408
|
+
if scope.arity > 0
|
409
|
+
raise ArgumentError, <<-MSG.squish
|
410
|
+
The association scope '#{name}' is instance dependent (the scope
|
411
|
+
block takes an argument). Preloading instance dependent scopes is
|
412
|
+
not supported.
|
413
|
+
MSG
|
248
414
|
end
|
249
415
|
end
|
416
|
+
alias :check_eager_loadable! :check_preloadable!
|
417
|
+
|
418
|
+
def join_id_for(owner) # :nodoc:
|
419
|
+
owner[active_record_primary_key]
|
420
|
+
end
|
250
421
|
|
251
422
|
def through_reflection
|
252
423
|
nil
|
253
424
|
end
|
254
425
|
|
255
426
|
def source_reflection
|
256
|
-
|
427
|
+
self
|
257
428
|
end
|
258
429
|
|
259
430
|
# A chain of reflections from this one back to the owner. For more see the explanation in
|
260
431
|
# ThroughReflection.
|
261
|
-
def
|
432
|
+
def collect_join_chain
|
262
433
|
[self]
|
263
434
|
end
|
264
435
|
|
436
|
+
# This is for clearing cache on the reflection. Useful for tests that need to compare
|
437
|
+
# SQL queries on associations.
|
438
|
+
def clear_association_scope_cache # :nodoc:
|
439
|
+
@association_scope_cache.clear
|
440
|
+
end
|
441
|
+
|
265
442
|
def nested?
|
266
443
|
false
|
267
444
|
end
|
268
445
|
|
269
|
-
# An array of arrays of
|
270
|
-
# in the #chain.
|
271
|
-
|
272
|
-
|
273
|
-
[[options[:conditions]].compact]
|
446
|
+
# An array of arrays of scopes. Each item in the outside array corresponds to a reflection
|
447
|
+
# in the #chain.
|
448
|
+
def scope_chain
|
449
|
+
scope ? [[scope]] : [[]]
|
274
450
|
end
|
275
451
|
|
276
|
-
|
277
|
-
|
278
|
-
def has_inverse?
|
279
|
-
@options[:inverse_of]
|
452
|
+
def has_scope?
|
453
|
+
scope
|
280
454
|
end
|
281
455
|
|
282
|
-
def
|
283
|
-
|
284
|
-
@inverse_of ||= klass.reflect_on_association(options[:inverse_of])
|
285
|
-
end
|
456
|
+
def has_inverse?
|
457
|
+
inverse_name
|
286
458
|
end
|
287
459
|
|
288
460
|
def polymorphic_inverse_of(associated_class)
|
289
461
|
if has_inverse?
|
290
|
-
if inverse_relationship = associated_class.
|
462
|
+
if inverse_relationship = associated_class._reflect_on_association(options[:inverse_of])
|
291
463
|
inverse_relationship
|
292
464
|
else
|
293
465
|
raise InverseOfAssociationNotFoundError.new(self, associated_class)
|
@@ -295,61 +467,138 @@ module ActiveRecord
|
|
295
467
|
end
|
296
468
|
end
|
297
469
|
|
470
|
+
# Returns the macro type.
|
471
|
+
#
|
472
|
+
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
|
473
|
+
def macro; raise NotImplementedError; end
|
474
|
+
|
298
475
|
# Returns whether or not this association reflection is for a collection
|
299
476
|
# association. Returns +true+ if the +macro+ is either +has_many+ or
|
300
477
|
# +has_and_belongs_to_many+, +false+ otherwise.
|
301
478
|
def collection?
|
302
|
-
|
479
|
+
false
|
303
480
|
end
|
304
481
|
|
305
482
|
# Returns whether or not the association should be validated as part of
|
306
483
|
# the parent's validation.
|
307
484
|
#
|
308
485
|
# Unless you explicitly disable validation with
|
309
|
-
# <tt
|
486
|
+
# <tt>validate: false</tt>, validation will take place when:
|
310
487
|
#
|
311
|
-
# * you explicitly enable validation; <tt
|
312
|
-
# * you use autosave; <tt
|
488
|
+
# * you explicitly enable validation; <tt>validate: true</tt>
|
489
|
+
# * you use autosave; <tt>autosave: true</tt>
|
313
490
|
# * the association is a +has_many+ association
|
314
491
|
def validate?
|
315
|
-
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true ||
|
492
|
+
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || collection?)
|
316
493
|
end
|
317
494
|
|
318
495
|
# Returns +true+ if +self+ is a +belongs_to+ reflection.
|
319
|
-
def belongs_to
|
320
|
-
|
496
|
+
def belongs_to?; false; end
|
497
|
+
|
498
|
+
# Returns +true+ if +self+ is a +has_one+ reflection.
|
499
|
+
def has_one?; false; end
|
500
|
+
|
501
|
+
def association_class; raise NotImplementedError; end
|
502
|
+
|
503
|
+
def polymorphic?
|
504
|
+
options[:polymorphic]
|
321
505
|
end
|
322
506
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
507
|
+
VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to]
|
508
|
+
INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key]
|
509
|
+
|
510
|
+
def add_as_source(seed)
|
511
|
+
seed
|
512
|
+
end
|
513
|
+
|
514
|
+
def add_as_polymorphic_through(reflection, seed)
|
515
|
+
seed + [PolymorphicReflection.new(self, reflection)]
|
516
|
+
end
|
517
|
+
|
518
|
+
def add_as_through(seed)
|
519
|
+
seed + [self]
|
520
|
+
end
|
521
|
+
|
522
|
+
protected
|
523
|
+
|
524
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
525
|
+
self
|
526
|
+
end
|
527
|
+
|
528
|
+
private
|
529
|
+
|
530
|
+
def calculate_constructable(macro, options)
|
531
|
+
true
|
532
|
+
end
|
533
|
+
|
534
|
+
# Attempts to find the inverse association name automatically.
|
535
|
+
# If it cannot find a suitable inverse association name, it returns
|
536
|
+
# nil.
|
537
|
+
def inverse_name
|
538
|
+
options.fetch(:inverse_of) do
|
539
|
+
if @automatic_inverse_of == false
|
540
|
+
nil
|
541
|
+
else
|
542
|
+
@automatic_inverse_of ||= automatic_inverse_of
|
543
|
+
end
|
338
544
|
end
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
545
|
+
end
|
546
|
+
|
547
|
+
# returns either false or the inverse association name that it finds.
|
548
|
+
def automatic_inverse_of
|
549
|
+
if can_find_inverse_of_automatically?(self)
|
550
|
+
inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
|
551
|
+
|
552
|
+
begin
|
553
|
+
reflection = klass._reflect_on_association(inverse_name)
|
554
|
+
rescue NameError
|
555
|
+
# Give up: we couldn't compute the klass type so we won't be able
|
556
|
+
# to find any associations either.
|
557
|
+
reflection = false
|
558
|
+
end
|
559
|
+
|
560
|
+
if valid_inverse_reflection?(reflection)
|
561
|
+
return inverse_name
|
562
|
+
end
|
344
563
|
end
|
564
|
+
|
565
|
+
false
|
566
|
+
end
|
567
|
+
|
568
|
+
# Checks if the inverse reflection that is returned from the
|
569
|
+
# +automatic_inverse_of+ method is a valid reflection. We must
|
570
|
+
# make sure that the reflection's active_record name matches up
|
571
|
+
# with the current reflection's klass name.
|
572
|
+
#
|
573
|
+
# Note: klass will always be valid because when there's a NameError
|
574
|
+
# from calling +klass+, +reflection+ will already be set to false.
|
575
|
+
def valid_inverse_reflection?(reflection)
|
576
|
+
reflection &&
|
577
|
+
klass.name == reflection.active_record.name &&
|
578
|
+
can_find_inverse_of_automatically?(reflection)
|
579
|
+
end
|
580
|
+
|
581
|
+
# Checks to see if the reflection doesn't have any options that prevent
|
582
|
+
# us from being able to guess the inverse automatically. First, the
|
583
|
+
# <tt>inverse_of</tt> option cannot be set to false. Second, we must
|
584
|
+
# have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations.
|
585
|
+
# Third, we must not have options such as <tt>:polymorphic</tt> or
|
586
|
+
# <tt>:foreign_key</tt> which prevent us from correctly guessing the
|
587
|
+
# inverse association.
|
588
|
+
#
|
589
|
+
# Anything with a scope can additionally ruin our attempt at finding an
|
590
|
+
# inverse, so we exclude reflections with scopes.
|
591
|
+
def can_find_inverse_of_automatically?(reflection)
|
592
|
+
reflection.options[:inverse_of] != false &&
|
593
|
+
VALID_AUTOMATIC_INVERSE_MACROS.include?(reflection.macro) &&
|
594
|
+
!INVALID_AUTOMATIC_INVERSE_OPTIONS.any? { |opt| reflection.options[opt] } &&
|
595
|
+
!reflection.scope
|
345
596
|
end
|
346
|
-
end
|
347
597
|
|
348
|
-
private
|
349
598
|
def derive_class_name
|
350
|
-
class_name = name.to_s
|
599
|
+
class_name = name.to_s
|
351
600
|
class_name = class_name.singularize if collection?
|
352
|
-
class_name
|
601
|
+
class_name.camelize
|
353
602
|
end
|
354
603
|
|
355
604
|
def derive_foreign_key
|
@@ -362,27 +611,130 @@ module ActiveRecord
|
|
362
611
|
end
|
363
612
|
end
|
364
613
|
|
614
|
+
def derive_join_table
|
615
|
+
ModelSchema.derive_join_table_name active_record.table_name, klass.table_name
|
616
|
+
end
|
617
|
+
|
365
618
|
def primary_key(klass)
|
366
619
|
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
367
620
|
end
|
368
621
|
end
|
369
622
|
|
623
|
+
class HasManyReflection < AssociationReflection # :nodoc:
|
624
|
+
def macro; :has_many; end
|
625
|
+
|
626
|
+
def collection?; true; end
|
627
|
+
|
628
|
+
def association_class
|
629
|
+
if options[:through]
|
630
|
+
Associations::HasManyThroughAssociation
|
631
|
+
else
|
632
|
+
Associations::HasManyAssociation
|
633
|
+
end
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
class HasOneReflection < AssociationReflection # :nodoc:
|
638
|
+
def macro; :has_one; end
|
639
|
+
|
640
|
+
def has_one?; true; end
|
641
|
+
|
642
|
+
def association_class
|
643
|
+
if options[:through]
|
644
|
+
Associations::HasOneThroughAssociation
|
645
|
+
else
|
646
|
+
Associations::HasOneAssociation
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
private
|
651
|
+
|
652
|
+
def calculate_constructable(macro, options)
|
653
|
+
!options[:through]
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
class BelongsToReflection < AssociationReflection # :nodoc:
|
658
|
+
def macro; :belongs_to; end
|
659
|
+
|
660
|
+
def belongs_to?; true; end
|
661
|
+
|
662
|
+
def association_class
|
663
|
+
if polymorphic?
|
664
|
+
Associations::BelongsToPolymorphicAssociation
|
665
|
+
else
|
666
|
+
Associations::BelongsToAssociation
|
667
|
+
end
|
668
|
+
end
|
669
|
+
|
670
|
+
def join_keys(association_klass)
|
671
|
+
key = polymorphic? ? association_primary_key(association_klass) : association_primary_key
|
672
|
+
JoinKeys.new(key, foreign_key)
|
673
|
+
end
|
674
|
+
|
675
|
+
def join_id_for(owner) # :nodoc:
|
676
|
+
owner[foreign_key]
|
677
|
+
end
|
678
|
+
|
679
|
+
private
|
680
|
+
|
681
|
+
def calculate_constructable(macro, options)
|
682
|
+
!polymorphic?
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
|
687
|
+
def initialize(name, scope, options, active_record)
|
688
|
+
super
|
689
|
+
end
|
690
|
+
|
691
|
+
def macro; :has_and_belongs_to_many; end
|
692
|
+
|
693
|
+
def collection?
|
694
|
+
true
|
695
|
+
end
|
696
|
+
end
|
697
|
+
|
370
698
|
# Holds all the meta-data about a :through association as it was specified
|
371
699
|
# in the Active Record class.
|
372
|
-
class ThroughReflection <
|
700
|
+
class ThroughReflection < AbstractReflection #:nodoc:
|
701
|
+
attr_reader :delegate_reflection
|
373
702
|
delegate :foreign_key, :foreign_type, :association_foreign_key,
|
374
703
|
:active_record_primary_key, :type, :to => :source_reflection
|
375
704
|
|
376
|
-
|
705
|
+
def initialize(delegate_reflection)
|
706
|
+
@delegate_reflection = delegate_reflection
|
707
|
+
@klass = delegate_reflection.options[:anonymous_class]
|
708
|
+
@source_reflection_name = delegate_reflection.options[:source]
|
709
|
+
end
|
710
|
+
|
711
|
+
def through_reflection?
|
712
|
+
true
|
713
|
+
end
|
714
|
+
|
715
|
+
def klass
|
716
|
+
@klass ||= delegate_reflection.compute_class(class_name)
|
717
|
+
end
|
718
|
+
|
719
|
+
# Returns the source of the through reflection. It checks both a singularized
|
377
720
|
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
|
378
721
|
#
|
379
722
|
# class Post < ActiveRecord::Base
|
380
723
|
# has_many :taggings
|
381
|
-
# has_many :tags, :
|
724
|
+
# has_many :tags, through: :taggings
|
725
|
+
# end
|
726
|
+
#
|
727
|
+
# class Tagging < ActiveRecord::Base
|
728
|
+
# belongs_to :post
|
729
|
+
# belongs_to :tag
|
382
730
|
# end
|
383
731
|
#
|
732
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
733
|
+
# tags_reflection.source_reflection
|
734
|
+
# # => <ActiveRecord::Reflection::BelongsToReflection: @name=:tag, @active_record=Tagging, @plural_name="tags">
|
735
|
+
#
|
384
736
|
def source_reflection
|
385
|
-
|
737
|
+
through_reflection.klass._reflect_on_association(source_reflection_name)
|
386
738
|
end
|
387
739
|
|
388
740
|
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
|
@@ -390,14 +742,15 @@ module ActiveRecord
|
|
390
742
|
#
|
391
743
|
# class Post < ActiveRecord::Base
|
392
744
|
# has_many :taggings
|
393
|
-
# has_many :tags, :
|
745
|
+
# has_many :tags, through: :taggings
|
394
746
|
# end
|
395
747
|
#
|
396
748
|
# tags_reflection = Post.reflect_on_association(:tags)
|
397
|
-
#
|
749
|
+
# tags_reflection.through_reflection
|
750
|
+
# # => <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @active_record=Post, @plural_name="taggings">
|
398
751
|
#
|
399
752
|
def through_reflection
|
400
|
-
|
753
|
+
active_record._reflect_on_association(options[:through])
|
401
754
|
end
|
402
755
|
|
403
756
|
# Returns an array of reflections which are involved in this association. Each item in the
|
@@ -406,64 +759,83 @@ module ActiveRecord
|
|
406
759
|
# The chain is built by recursively calling #chain on the source reflection and the through
|
407
760
|
# reflection. The base case for the recursion is a normal association, which just returns
|
408
761
|
# [self] as its #chain.
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
762
|
+
#
|
763
|
+
# class Post < ActiveRecord::Base
|
764
|
+
# has_many :taggings
|
765
|
+
# has_many :tags, through: :taggings
|
766
|
+
# end
|
767
|
+
#
|
768
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
769
|
+
# tags_reflection.chain
|
770
|
+
# # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>,
|
771
|
+
# <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>]
|
772
|
+
#
|
773
|
+
def collect_join_chain
|
774
|
+
collect_join_reflections [self]
|
775
|
+
end
|
776
|
+
|
777
|
+
# This is for clearing cache on the reflection. Useful for tests that need to compare
|
778
|
+
# SQL queries on associations.
|
779
|
+
def clear_association_scope_cache # :nodoc:
|
780
|
+
delegate_reflection.clear_association_scope_cache
|
781
|
+
source_reflection.clear_association_scope_cache
|
782
|
+
through_reflection.clear_association_scope_cache
|
415
783
|
end
|
416
784
|
|
417
785
|
# Consider the following example:
|
418
786
|
#
|
419
787
|
# class Person
|
420
788
|
# has_many :articles
|
421
|
-
# has_many :comment_tags, :
|
789
|
+
# has_many :comment_tags, through: :articles
|
422
790
|
# end
|
423
791
|
#
|
424
792
|
# class Article
|
425
793
|
# has_many :comments
|
426
|
-
# has_many :comment_tags, :
|
794
|
+
# has_many :comment_tags, through: :comments, source: :tags
|
427
795
|
# end
|
428
796
|
#
|
429
797
|
# class Comment
|
430
798
|
# has_many :tags
|
431
799
|
# end
|
432
800
|
#
|
433
|
-
# There may be
|
801
|
+
# There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
|
434
802
|
# 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 }
|
803
|
+
# of scopes corresponding to the chain.
|
804
|
+
def scope_chain
|
805
|
+
@scope_chain ||= begin
|
806
|
+
scope_chain = source_reflection.scope_chain.map(&:dup)
|
441
807
|
|
442
|
-
# Add to it the
|
443
|
-
|
808
|
+
# Add to it the scope from this reflection (if any)
|
809
|
+
scope_chain.first << scope if scope
|
444
810
|
|
445
|
-
|
811
|
+
through_scope_chain = through_reflection.scope_chain.map(&:dup)
|
446
812
|
|
447
813
|
if options[:source_type]
|
448
|
-
|
814
|
+
type = foreign_type
|
815
|
+
source_type = options[:source_type]
|
816
|
+
through_scope_chain.first << lambda { |object|
|
817
|
+
where(type => source_type)
|
818
|
+
}
|
449
819
|
end
|
450
820
|
|
451
821
|
# Recursively fill out the rest of the array from the through reflection
|
452
|
-
|
453
|
-
|
454
|
-
# And return
|
455
|
-
conditions
|
822
|
+
scope_chain + through_scope_chain
|
456
823
|
end
|
457
824
|
end
|
458
825
|
|
459
|
-
|
460
|
-
|
461
|
-
|
826
|
+
def has_scope?
|
827
|
+
scope || options[:source_type] ||
|
828
|
+
source_reflection.has_scope? ||
|
829
|
+
through_reflection.has_scope?
|
830
|
+
end
|
831
|
+
|
832
|
+
def join_keys(association_klass)
|
833
|
+
source_reflection.join_keys(association_klass)
|
462
834
|
end
|
463
835
|
|
464
836
|
# A through association is nested if there would be more than one join table
|
465
837
|
def nested?
|
466
|
-
|
838
|
+
source_reflection.through_reflection? || through_reflection.through_reflection?
|
467
839
|
end
|
468
840
|
|
469
841
|
# We want to use the klass from this reflection, rather than just delegate straight to
|
@@ -472,20 +844,45 @@ module ActiveRecord
|
|
472
844
|
def association_primary_key(klass = nil)
|
473
845
|
# Get the "actual" source reflection if the immediate source reflection has a
|
474
846
|
# 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)
|
847
|
+
actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass)
|
481
848
|
end
|
482
849
|
|
483
|
-
# Gets an array of possible <tt>:through</tt> source reflection names
|
850
|
+
# Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form.
|
851
|
+
#
|
852
|
+
# class Post < ActiveRecord::Base
|
853
|
+
# has_many :taggings
|
854
|
+
# has_many :tags, through: :taggings
|
855
|
+
# end
|
484
856
|
#
|
485
|
-
#
|
857
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
858
|
+
# tags_reflection.source_reflection_names
|
859
|
+
# # => [:tag, :tags]
|
486
860
|
#
|
487
861
|
def source_reflection_names
|
488
|
-
|
862
|
+
options[:source] ? [options[:source]] : [name.to_s.singularize, name].uniq
|
863
|
+
end
|
864
|
+
|
865
|
+
def source_reflection_name # :nodoc:
|
866
|
+
return @source_reflection_name if @source_reflection_name
|
867
|
+
|
868
|
+
names = [name.to_s.singularize, name].collect(&:to_sym).uniq
|
869
|
+
names = names.find_all { |n|
|
870
|
+
through_reflection.klass._reflect_on_association(n)
|
871
|
+
}
|
872
|
+
|
873
|
+
if names.length > 1
|
874
|
+
example_options = options.dup
|
875
|
+
example_options[:source] = source_reflection_names.first
|
876
|
+
ActiveSupport::Deprecation.warn \
|
877
|
+
"Ambiguous source reflection for through association. Please " \
|
878
|
+
"specify a :source directive on your declaration like:\n" \
|
879
|
+
"\n" \
|
880
|
+
" class #{active_record.name} < ActiveRecord::Base\n" \
|
881
|
+
" #{macro} :#{name}, #{example_options}\n" \
|
882
|
+
" end"
|
883
|
+
end
|
884
|
+
|
885
|
+
@source_reflection_name = names.first
|
489
886
|
end
|
490
887
|
|
491
888
|
def source_options
|
@@ -496,39 +893,168 @@ module ActiveRecord
|
|
496
893
|
through_reflection.options
|
497
894
|
end
|
498
895
|
|
896
|
+
def join_id_for(owner) # :nodoc:
|
897
|
+
source_reflection.join_id_for(owner)
|
898
|
+
end
|
899
|
+
|
499
900
|
def check_validity!
|
500
901
|
if through_reflection.nil?
|
501
902
|
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
|
502
903
|
end
|
503
904
|
|
504
|
-
if through_reflection.
|
505
|
-
|
905
|
+
if through_reflection.polymorphic?
|
906
|
+
if has_one?
|
907
|
+
raise HasOneAssociationPolymorphicThroughError.new(active_record.name, self)
|
908
|
+
else
|
909
|
+
raise HasManyThroughAssociationPolymorphicThroughError.new(active_record.name, self)
|
910
|
+
end
|
506
911
|
end
|
507
912
|
|
508
913
|
if source_reflection.nil?
|
509
914
|
raise HasManyThroughSourceAssociationNotFoundError.new(self)
|
510
915
|
end
|
511
916
|
|
512
|
-
if options[:source_type] && source_reflection.
|
917
|
+
if options[:source_type] && !source_reflection.polymorphic?
|
513
918
|
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
|
514
919
|
end
|
515
920
|
|
516
|
-
if source_reflection.
|
921
|
+
if source_reflection.polymorphic? && options[:source_type].nil?
|
517
922
|
raise HasManyThroughAssociationPolymorphicSourceError.new(active_record.name, self, source_reflection)
|
518
923
|
end
|
519
924
|
|
520
|
-
if
|
925
|
+
if has_one? && through_reflection.collection?
|
521
926
|
raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
|
522
927
|
end
|
523
928
|
|
524
929
|
check_validity_of_inverse!
|
525
930
|
end
|
526
931
|
|
932
|
+
def constraints
|
933
|
+
scope_chain = source_reflection.constraints
|
934
|
+
scope_chain << scope if scope
|
935
|
+
scope_chain
|
936
|
+
end
|
937
|
+
|
938
|
+
def add_as_source(seed)
|
939
|
+
collect_join_reflections seed
|
940
|
+
end
|
941
|
+
|
942
|
+
def add_as_polymorphic_through(reflection, seed)
|
943
|
+
collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)])
|
944
|
+
end
|
945
|
+
|
946
|
+
def add_as_through(seed)
|
947
|
+
collect_join_reflections(seed + [self])
|
948
|
+
end
|
949
|
+
|
950
|
+
def collect_join_reflections(seed)
|
951
|
+
a = source_reflection.add_as_source seed
|
952
|
+
if options[:source_type]
|
953
|
+
through_reflection.add_as_polymorphic_through self, a
|
954
|
+
else
|
955
|
+
through_reflection.add_as_through a
|
956
|
+
end
|
957
|
+
end
|
958
|
+
|
959
|
+
protected
|
960
|
+
|
961
|
+
def actual_source_reflection # FIXME: this is a horrible name
|
962
|
+
source_reflection.send(:actual_source_reflection)
|
963
|
+
end
|
964
|
+
|
965
|
+
def primary_key(klass)
|
966
|
+
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
|
967
|
+
end
|
968
|
+
|
969
|
+
def inverse_name; delegate_reflection.send(:inverse_name); end
|
970
|
+
|
527
971
|
private
|
528
972
|
def derive_class_name
|
529
973
|
# get the class_name of the belongs_to association of the through reflection
|
530
974
|
options[:source_type] || source_reflection.class_name
|
531
975
|
end
|
976
|
+
|
977
|
+
delegate_methods = AssociationReflection.public_instance_methods -
|
978
|
+
public_instance_methods
|
979
|
+
|
980
|
+
delegate(*delegate_methods, to: :delegate_reflection)
|
981
|
+
|
982
|
+
end
|
983
|
+
|
984
|
+
class PolymorphicReflection < ThroughReflection # :nodoc:
|
985
|
+
def initialize(reflection, previous_reflection)
|
986
|
+
@reflection = reflection
|
987
|
+
@previous_reflection = previous_reflection
|
988
|
+
end
|
989
|
+
|
990
|
+
def klass
|
991
|
+
@reflection.klass
|
992
|
+
end
|
993
|
+
|
994
|
+
def scope
|
995
|
+
@reflection.scope
|
996
|
+
end
|
997
|
+
|
998
|
+
def table_name
|
999
|
+
@reflection.table_name
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
def plural_name
|
1003
|
+
@reflection.plural_name
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
def join_keys(association_klass)
|
1007
|
+
@reflection.join_keys(association_klass)
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
def type
|
1011
|
+
@reflection.type
|
1012
|
+
end
|
1013
|
+
|
1014
|
+
def constraints
|
1015
|
+
@reflection.constraints + [source_type_info]
|
1016
|
+
end
|
1017
|
+
|
1018
|
+
def source_type_info
|
1019
|
+
type = @previous_reflection.foreign_type
|
1020
|
+
source_type = @previous_reflection.options[:source_type]
|
1021
|
+
lambda { |object| where(type => source_type) }
|
1022
|
+
end
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
class RuntimeReflection < PolymorphicReflection # :nodoc:
|
1026
|
+
attr_accessor :next
|
1027
|
+
|
1028
|
+
def initialize(reflection, association)
|
1029
|
+
@reflection = reflection
|
1030
|
+
@association = association
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
def klass
|
1034
|
+
@association.klass
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
def table_name
|
1038
|
+
klass.table_name
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
def constraints
|
1042
|
+
@reflection.constraints
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
def source_type_info
|
1046
|
+
@reflection.source_type_info
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
def alias_candidate(name)
|
1050
|
+
"#{plural_name}_#{name}_join"
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
def alias_name
|
1054
|
+
Arel::Table.new(table_name)
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
def all_includes; yield; end
|
532
1058
|
end
|
533
1059
|
end
|
534
1060
|
end
|