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.
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