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