activerecord 4.2.11 → 5.2.4.1

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.

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