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