activerecord 3.2.22.5 → 5.2.8

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

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