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,214 +1,262 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class JoinDependency # :nodoc:
4
- autoload :JoinPart, 'active_record/associations/join_dependency/join_part'
5
- autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
6
- autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
7
-
8
- attr_reader :join_parts, :reflections, :alias_tracker, :active_record
9
-
10
- def initialize(base, associations, joins)
11
- @active_record = base
12
- @table_joins = joins
13
- @join_parts = [JoinBase.new(base)]
14
- @associations = {}
15
- @reflections = []
16
- @alias_tracker = AliasTracker.new(base.connection, joins)
17
- @alias_tracker.aliased_name_for(base.table_name) # Updates the count for base.table_name to 1
18
- build(associations)
19
- end
6
+ autoload :JoinBase, "active_record/associations/join_dependency/join_base"
7
+ autoload :JoinAssociation, "active_record/associations/join_dependency/join_association"
20
8
 
21
- def graft(*associations)
22
- associations.each do |association|
23
- join_associations.detect {|a| association == a} ||
24
- build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
9
+ class Aliases # :nodoc:
10
+ def initialize(tables)
11
+ @tables = tables
12
+ @alias_cache = tables.each_with_object({}) { |table, h|
13
+ h[table.node] = table.columns.each_with_object({}) { |column, i|
14
+ i[column.name] = column.alias
15
+ }
16
+ }
17
+ @name_and_alias_cache = tables.each_with_object({}) { |table, h|
18
+ h[table.node] = table.columns.map { |column|
19
+ [column.name, column.alias]
20
+ }
21
+ }
25
22
  end
26
- self
27
- end
28
-
29
- def join_associations
30
- join_parts.last(join_parts.length - 1)
31
- end
32
23
 
33
- def join_base
34
- join_parts.first
35
- end
24
+ def columns
25
+ @tables.flat_map(&:column_aliases)
26
+ end
36
27
 
37
- def columns
38
- join_parts.collect { |join_part|
39
- table = join_part.aliased_table
40
- join_part.column_names_with_alias.collect{ |column_name, aliased_name|
41
- table[column_name].as Arel.sql(aliased_name)
42
- }
43
- }.flatten
44
- end
28
+ # An array of [column_name, alias] pairs for the table
29
+ def column_aliases(node)
30
+ @name_and_alias_cache[node]
31
+ end
45
32
 
46
- def instantiate(rows)
47
- primary_key = join_base.aliased_primary_key
48
- parents = {}
33
+ def column_alias(node, column)
34
+ @alias_cache[node][column]
35
+ end
49
36
 
50
- records = rows.map { |model|
51
- primary_id = model[primary_key]
52
- parent = parents[primary_id] ||= join_base.instantiate(model)
53
- construct(parent, @associations, join_associations, model)
54
- parent
55
- }.uniq
37
+ Table = Struct.new(:node, :columns) do # :nodoc:
38
+ def column_aliases
39
+ t = node.table
40
+ columns.map { |column| t[column.name].as Arel.sql column.alias }
41
+ end
42
+ end
43
+ Column = Struct.new(:name, :alias)
44
+ end
56
45
 
57
- remove_duplicate_results!(active_record, records, @associations)
58
- records
46
+ def self.make_tree(associations)
47
+ hash = {}
48
+ walk_tree associations, hash
49
+ hash
59
50
  end
60
51
 
61
- def remove_duplicate_results!(base, records, associations)
52
+ def self.walk_tree(associations, hash)
62
53
  case associations
63
54
  when Symbol, String
64
- reflection = base.reflections[associations]
65
- remove_uniq_by_reflection(reflection, records)
55
+ hash[associations.to_sym] ||= {}
66
56
  when Array
67
- associations.each do |association|
68
- remove_duplicate_results!(base, records, association)
57
+ associations.each do |assoc|
58
+ walk_tree assoc, hash
69
59
  end
70
60
  when Hash
71
- associations.keys.each do |name|
72
- reflection = base.reflections[name]
73
- remove_uniq_by_reflection(reflection, records)
74
-
75
- parent_records = []
76
- records.each do |record|
77
- if descendant = record.send(reflection.name)
78
- if reflection.collection?
79
- parent_records.concat descendant.target.uniq
80
- else
81
- parent_records << descendant
82
- end
83
- end
84
- end
61
+ associations.each do |k, v|
62
+ cache = hash[k] ||= {}
63
+ walk_tree v, cache
64
+ end
65
+ else
66
+ raise ConfigurationError, associations.inspect
67
+ end
68
+ end
85
69
 
86
- remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty?
70
+ def initialize(base, table, associations)
71
+ tree = self.class.make_tree associations
72
+ @join_root = JoinBase.new(base, table, build(tree, base))
73
+ end
74
+
75
+ def reflections
76
+ join_root.drop(1).map!(&:reflection)
77
+ end
78
+
79
+ def join_constraints(joins_to_add, join_type, alias_tracker)
80
+ @alias_tracker = alias_tracker
81
+
82
+ construct_tables!(join_root)
83
+ joins = make_join_constraints(join_root, join_type)
84
+
85
+ joins.concat joins_to_add.flat_map { |oj|
86
+ construct_tables!(oj.join_root)
87
+ if join_root.match? oj.join_root
88
+ walk join_root, oj.join_root
89
+ else
90
+ make_join_constraints(oj.join_root, join_type)
87
91
  end
92
+ }
93
+ end
94
+
95
+ def instantiate(result_set, &block)
96
+ primary_key = aliases.column_alias(join_root, join_root.primary_key)
97
+
98
+ seen = Hash.new { |i, object_id|
99
+ i[object_id] = Hash.new { |j, child_class|
100
+ j[child_class] = {}
101
+ }
102
+ }
103
+
104
+ model_cache = Hash.new { |h, klass| h[klass] = {} }
105
+ parents = model_cache[join_root]
106
+ column_aliases = aliases.column_aliases join_root
107
+
108
+ message_bus = ActiveSupport::Notifications.instrumenter
109
+
110
+ payload = {
111
+ record_count: result_set.length,
112
+ class_name: join_root.base_klass.name
113
+ }
114
+
115
+ message_bus.instrument("instantiation.active_record", payload) do
116
+ result_set.each { |row_hash|
117
+ parent_key = primary_key ? row_hash[primary_key] : row_hash
118
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, &block)
119
+ construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
120
+ }
88
121
  end
122
+
123
+ parents.values
124
+ end
125
+
126
+ def apply_column_aliases(relation)
127
+ relation._select!(-> { aliases.columns })
89
128
  end
90
129
 
91
130
  protected
131
+ attr_reader :alias_tracker, :join_root
92
132
 
93
- def cache_joined_association(association)
94
- associations = []
95
- parent = association.parent
96
- while parent != join_base
97
- associations.unshift(parent.reflection.name)
98
- parent = parent.parent
99
- end
100
- ref = @associations
101
- associations.each do |key|
102
- ref = ref[key]
133
+ private
134
+ def aliases
135
+ @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
136
+ columns = join_part.column_names.each_with_index.map { |column_name, j|
137
+ Aliases::Column.new column_name, "t#{i}_r#{j}"
138
+ }
139
+ Aliases::Table.new(join_part, columns)
140
+ }
103
141
  end
104
- ref[association.reflection.name] ||= {}
105
- end
106
142
 
107
- def build(associations, parent = nil, join_type = Arel::InnerJoin)
108
- parent ||= join_parts.last
109
- case associations
110
- when Symbol, String
111
- reflection = parent.reflections[associations.to_s.intern] or
112
- raise ConfigurationError, "Association named '#{ associations }' was not found on #{parent.active_record.name}; perhaps you misspelled it?"
113
- unless join_association = find_join_association(reflection, parent)
114
- @reflections << reflection
115
- join_association = build_join_association(reflection, parent)
116
- join_association.join_type = join_type
117
- @join_parts << join_association
118
- cache_joined_association(join_association)
143
+ def construct_tables!(join_root)
144
+ join_root.each_children do |parent, child|
145
+ child.tables = table_aliases_for(parent, child)
119
146
  end
120
- join_association
121
- when Array
122
- associations.each do |association|
123
- build(association, parent, join_type)
124
- end
125
- when Hash
126
- associations.keys.sort_by { |a| a.to_s }.each do |name|
127
- join_association = build(name, parent, join_type)
128
- build(associations[name], join_association, join_type)
147
+ end
148
+
149
+ def make_join_constraints(join_root, join_type)
150
+ join_root.children.flat_map do |child|
151
+ make_constraints(join_root, child, join_type)
129
152
  end
130
- else
131
- raise ConfigurationError, associations.inspect
132
153
  end
133
- end
134
154
 
135
- def find_join_association(name_or_reflection, parent)
136
- if String === name_or_reflection
137
- name_or_reflection = name_or_reflection.to_sym
155
+ def make_constraints(parent, child, join_type = Arel::Nodes::OuterJoin)
156
+ foreign_table = parent.table
157
+ foreign_klass = parent.base_klass
158
+ joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
159
+ joins.concat child.children.flat_map { |c| make_constraints(child, c, join_type) }
138
160
  end
139
161
 
140
- join_associations.detect { |j|
141
- j.reflection == name_or_reflection && j.parent == parent
142
- }
143
- end
162
+ def table_aliases_for(parent, node)
163
+ node.reflection.chain.map { |reflection|
164
+ alias_tracker.aliased_table_for(
165
+ reflection.table_name,
166
+ table_alias_for(reflection, parent, reflection != node.reflection),
167
+ reflection.klass.type_caster
168
+ )
169
+ }
170
+ end
144
171
 
145
- def remove_uniq_by_reflection(reflection, records)
146
- if reflection && reflection.collection?
147
- records.each { |record| record.send(reflection.name).target.uniq! }
172
+ def table_alias_for(reflection, parent, join)
173
+ name = "#{reflection.plural_name}_#{parent.table_name}"
174
+ join ? "#{name}_join" : name
148
175
  end
149
- end
150
176
 
151
- def build_join_association(reflection, parent)
152
- JoinAssociation.new(reflection, self, parent)
153
- end
177
+ def walk(left, right)
178
+ intersection, missing = right.children.map { |node1|
179
+ [left.children.find { |node2| node1.match? node2 }, node1]
180
+ }.partition(&:first)
154
181
 
155
- def construct(parent, associations, join_parts, row)
156
- case associations
157
- when Symbol, String
158
- name = associations.to_s
182
+ joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r) }
183
+ joins.concat missing.flat_map { |_, n| make_constraints(left, n) }
184
+ end
185
+
186
+ def find_reflection(klass, name)
187
+ klass._reflect_on_association(name) ||
188
+ raise(ConfigurationError, "Can't join '#{klass.name}' to association named '#{name}'; perhaps you misspelled it?")
189
+ end
159
190
 
160
- join_part = join_parts.detect { |j|
161
- j.reflection.name.to_s == name &&
162
- j.parent_table_name == parent.class.table_name }
191
+ def build(associations, base_klass)
192
+ associations.map do |name, right|
193
+ reflection = find_reflection base_klass, name
194
+ reflection.check_validity!
195
+ reflection.check_eager_loadable!
163
196
 
164
- raise(ConfigurationError, "No such association") unless join_part
197
+ if reflection.polymorphic?
198
+ raise EagerLoadPolymorphicError.new(reflection)
199
+ end
165
200
 
166
- join_parts.delete(join_part)
167
- construct_association(parent, join_part, row)
168
- when Array
169
- associations.each do |association|
170
- construct(parent, association, join_parts, row)
201
+ JoinAssociation.new(reflection, build(right, reflection.klass))
171
202
  end
172
- when Hash
173
- associations.sort_by { |k,_| k.to_s }.each do |association_name, assoc|
174
- association = construct(parent, association_name, join_parts, row)
175
- construct(association, assoc, join_parts, row) if association
203
+ end
204
+
205
+ def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
206
+ return if ar_parent.nil?
207
+
208
+ parent.children.each do |node|
209
+ if node.reflection.collection?
210
+ other = ar_parent.association(node.reflection.name)
211
+ other.loaded!
212
+ elsif ar_parent.association_cached?(node.reflection.name)
213
+ model = ar_parent.association(node.reflection.name).target
214
+ construct(model, node, row, rs, seen, model_cache, aliases)
215
+ next
216
+ end
217
+
218
+ key = aliases.column_alias(node, node.primary_key)
219
+ id = row[key]
220
+ if id.nil?
221
+ nil_association = ar_parent.association(node.reflection.name)
222
+ nil_association.loaded!
223
+ next
224
+ end
225
+
226
+ model = seen[ar_parent.object_id][node][id]
227
+
228
+ if model
229
+ construct(model, node, row, rs, seen, model_cache, aliases)
230
+ else
231
+ model = construct_model(ar_parent, node, row, model_cache, id, aliases)
232
+
233
+ if node.reflection.scope &&
234
+ node.reflection.scope_for(node.base_klass.unscoped).readonly_value
235
+ model.readonly!
236
+ end
237
+
238
+ seen[ar_parent.object_id][node][id] = model
239
+ construct(model, node, row, rs, seen, model_cache, aliases)
240
+ end
176
241
  end
177
- else
178
- raise ConfigurationError, associations.inspect
179
242
  end
180
- end
181
243
 
182
- def construct_association(record, join_part, row)
183
- return if record.id.to_s != join_part.parent.record_id(row).to_s
244
+ def construct_model(record, node, row, model_cache, id, aliases)
245
+ other = record.association(node.reflection.name)
184
246
 
185
- macro = join_part.reflection.macro
186
- if macro == :has_one
187
- return record.association(join_part.reflection.name).target if record.association_cache.key?(join_part.reflection.name)
188
- association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
189
- set_target_and_inverse(join_part, association, record)
190
- else
191
- association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil?
192
- case macro
193
- when :has_many, :has_and_belongs_to_many
194
- other = record.association(join_part.reflection.name)
195
- other.loaded!
196
- other.target.push(association) if association
197
- other.set_inverse_instance(association)
198
- when :belongs_to
199
- set_target_and_inverse(join_part, association, record)
247
+ model = model_cache[node][id] ||=
248
+ node.instantiate(row, aliases.column_aliases(node)) do |m|
249
+ other.set_inverse_instance(m)
250
+ end
251
+
252
+ if node.reflection.collection?
253
+ other.target.push(model)
200
254
  else
201
- raise ConfigurationError, "unknown macro: #{join_part.reflection.macro}"
255
+ other.target = model
202
256
  end
203
- end
204
- association
205
- end
206
257
 
207
- def set_target_and_inverse(join_part, association, record)
208
- other = record.association(join_part.reflection.name)
209
- other.target = association
210
- other.set_inverse_instance(association)
211
- end
258
+ model
259
+ end
212
260
  end
213
261
  end
214
262
  end
@@ -1,124 +1,130 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class Preloader
4
6
  class Association #:nodoc:
5
- attr_reader :owners, :reflection, :preload_options, :model, :klass
6
-
7
- def initialize(klass, owners, reflection, preload_options)
8
- @klass = klass
9
- @owners = owners
10
- @reflection = reflection
11
- @preload_options = preload_options || {}
12
- @model = owners.first && owners.first.class
13
- @scoped = nil
14
- @owners_by_key = nil
7
+ attr_reader :preloaded_records
8
+
9
+ def initialize(klass, owners, reflection, preload_scope)
10
+ @klass = klass
11
+ @owners = owners
12
+ @reflection = reflection
13
+ @preload_scope = preload_scope
14
+ @model = owners.first && owners.first.class
15
+ @preloaded_records = []
15
16
  end
16
17
 
17
- def run
18
- unless owners.first.association(reflection.name).loaded?
19
- preload
18
+ def run(preloader)
19
+ records = load_records do |record|
20
+ owner = owners_by_key[convert_key(record[association_key_name])]
21
+ association = owner.association(reflection.name)
22
+ association.set_inverse_instance(record)
20
23
  end
21
- end
22
24
 
23
- def preload
24
- raise NotImplementedError
25
- end
26
-
27
- def scoped
28
- @scoped ||= build_scope
25
+ owners.each do |owner|
26
+ associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || [])
27
+ end
29
28
  end
30
29
 
31
- def records_for(ids)
32
- scoped.where(association_key.in(ids))
33
- end
30
+ protected
31
+ attr_reader :owners, :reflection, :preload_scope, :model, :klass
34
32
 
35
- def table
36
- klass.arel_table
37
- end
33
+ private
34
+ # The name of the key on the associated records
35
+ def association_key_name
36
+ reflection.join_primary_key(klass)
37
+ end
38
38
 
39
- # The name of the key on the associated records
40
- def association_key_name
41
- raise NotImplementedError
42
- end
39
+ # The name of the key on the model which declares the association
40
+ def owner_key_name
41
+ reflection.join_foreign_key
42
+ end
43
43
 
44
- # This is overridden by HABTM as the condition should be on the foreign_key column in
45
- # the join table
46
- def association_key
47
- table[association_key_name]
48
- end
44
+ def associate_records_to_owner(owner, records)
45
+ association = owner.association(reflection.name)
46
+ association.loaded!
47
+ if reflection.collection?
48
+ association.target.concat(records)
49
+ else
50
+ association.target = records.first unless records.empty?
51
+ end
52
+ end
49
53
 
50
- # The name of the key on the model which declares the association
51
- def owner_key_name
52
- raise NotImplementedError
53
- end
54
+ def owner_keys
55
+ @owner_keys ||= owners_by_key.keys
56
+ end
54
57
 
55
- # We're converting to a string here because postgres will return the aliased association
56
- # key in a habtm as a string (for whatever reason)
57
- def owners_by_key
58
- @owners_by_key ||= owners.group_by do |owner|
59
- key = owner[owner_key_name]
60
- key && key.to_s
58
+ def owners_by_key
59
+ unless defined?(@owners_by_key)
60
+ @owners_by_key = owners.each_with_object({}) do |owner, h|
61
+ key = convert_key(owner[owner_key_name])
62
+ h[key] = owner if key
63
+ end
64
+ end
65
+ @owners_by_key
61
66
  end
62
- end
63
67
 
64
- def options
65
- reflection.options
66
- end
68
+ def key_conversion_required?
69
+ unless defined?(@key_conversion_required)
70
+ @key_conversion_required = (association_key_type != owner_key_type)
71
+ end
67
72
 
68
- private
73
+ @key_conversion_required
74
+ end
69
75
 
70
- def associated_records_by_owner
71
- owners_map = owners_by_key
72
- owner_keys = owners_map.keys.compact
76
+ def convert_key(key)
77
+ if key_conversion_required?
78
+ key.to_s
79
+ else
80
+ key
81
+ end
82
+ end
73
83
 
74
- if klass.nil? || owner_keys.empty?
75
- records = []
76
- else
77
- # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
78
- # Make several smaller queries if necessary or make one query if the adapter supports it
79
- sliced = owner_keys.each_slice(model.connection.in_clause_length || owner_keys.size)
80
- records = sliced.map { |slice| records_for(slice) }.flatten
84
+ def association_key_type
85
+ @klass.type_for_attribute(association_key_name).type
81
86
  end
82
87
 
83
- # Each record may have multiple owners, and vice-versa
84
- records_by_owner = Hash[owners.map { |owner| [owner, []] }]
85
- records.each do |record|
86
- owner_key = record[association_key_name].to_s
88
+ def owner_key_type
89
+ @model.type_for_attribute(owner_key_name).type
90
+ end
87
91
 
88
- owners_map[owner_key].each do |owner|
89
- records_by_owner[owner] << record
92
+ def load_records(&block)
93
+ return {} if owner_keys.empty?
94
+ # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
95
+ # Make several smaller queries if necessary or make one query if the adapter supports it
96
+ slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
97
+ @preloaded_records = slices.flat_map do |slice|
98
+ records_for(slice, &block)
99
+ end
100
+ @preloaded_records.group_by do |record|
101
+ convert_key(record[association_key_name])
90
102
  end
91
103
  end
92
- records_by_owner
93
- end
94
-
95
- def build_scope
96
- scope = klass.scoped
97
104
 
98
- scope = scope.where(process_conditions(options[:conditions]))
99
- scope = scope.where(process_conditions(preload_options[:conditions]))
105
+ def records_for(ids, &block)
106
+ scope.where(association_key_name => ids).load(&block)
107
+ end
100
108
 
101
- scope = scope.select(preload_options[:select] || options[:select] || table[Arel.star])
102
- scope = scope.includes(preload_options[:include] || options[:include])
109
+ def scope
110
+ @scope ||= build_scope
111
+ end
103
112
 
104
- if options[:as]
105
- scope = scope.where(
106
- klass.table_name => {
107
- reflection.type => model.base_class.sti_name
108
- }
109
- )
113
+ def reflection_scope
114
+ @reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped
110
115
  end
111
116
 
112
- scope
113
- end
117
+ def build_scope
118
+ scope = klass.scope_for_association
114
119
 
115
- def process_conditions(conditions)
116
- if conditions.respond_to?(:to_proc) && !conditions.is_a?(Hash)
117
- conditions = klass.send(:instance_eval, &conditions)
118
- end
120
+ if reflection.type
121
+ scope.where!(reflection.type => model.polymorphic_name)
122
+ end
119
123
 
120
- conditions
121
- end
124
+ scope.merge!(reflection_scope) if reflection.scope
125
+ scope.merge!(preload_scope) if preload_scope
126
+ scope
127
+ end
122
128
  end
123
129
  end
124
130
  end