activerecord 5.1.5 → 5.2.8.1

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

Potentially problematic release.


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

Files changed (261) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +655 -608
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -5
  5. data/examples/performance.rb +2 -0
  6. data/examples/simple.rb +2 -0
  7. data/lib/active_record/aggregations.rb +6 -5
  8. data/lib/active_record/association_relation.rb +7 -5
  9. data/lib/active_record/associations/alias_tracker.rb +19 -27
  10. data/lib/active_record/associations/association.rb +41 -37
  11. data/lib/active_record/associations/association_scope.rb +38 -50
  12. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +4 -7
  15. data/lib/active_record/associations/builder/belongs_to.rb +14 -5
  16. data/lib/active_record/associations/builder/collection_association.rb +3 -3
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -1
  18. data/lib/active_record/associations/builder/has_many.rb +2 -0
  19. data/lib/active_record/associations/builder/has_one.rb +2 -0
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  21. data/lib/active_record/associations/collection_association.rb +59 -47
  22. data/lib/active_record/associations/collection_proxy.rb +20 -49
  23. data/lib/active_record/associations/foreign_association.rb +2 -0
  24. data/lib/active_record/associations/has_many_association.rb +12 -1
  25. data/lib/active_record/associations/has_many_through_association.rb +36 -30
  26. data/lib/active_record/associations/has_one_association.rb +12 -1
  27. data/lib/active_record/associations/has_one_through_association.rb +13 -8
  28. data/lib/active_record/associations/join_dependency/join_association.rb +39 -63
  29. data/lib/active_record/associations/join_dependency/join_base.rb +9 -8
  30. data/lib/active_record/associations/join_dependency/join_part.rb +9 -9
  31. data/lib/active_record/associations/join_dependency.rb +48 -93
  32. data/lib/active_record/associations/preloader/association.rb +45 -61
  33. data/lib/active_record/associations/preloader/through_association.rb +71 -79
  34. data/lib/active_record/associations/preloader.rb +18 -38
  35. data/lib/active_record/associations/singular_association.rb +14 -16
  36. data/lib/active_record/associations/through_association.rb +26 -11
  37. data/lib/active_record/associations.rb +40 -63
  38. data/lib/active_record/attribute_assignment.rb +2 -5
  39. data/lib/active_record/attribute_decorators.rb +3 -2
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -0
  41. data/lib/active_record/attribute_methods/dirty.rb +32 -216
  42. data/lib/active_record/attribute_methods/primary_key.rb +7 -6
  43. data/lib/active_record/attribute_methods/query.rb +2 -0
  44. data/lib/active_record/attribute_methods/read.rb +9 -3
  45. data/lib/active_record/attribute_methods/serialization.rb +23 -0
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +6 -8
  47. data/lib/active_record/attribute_methods/write.rb +21 -9
  48. data/lib/active_record/attribute_methods.rb +65 -24
  49. data/lib/active_record/attributes.rb +7 -6
  50. data/lib/active_record/autosave_association.rb +35 -19
  51. data/lib/active_record/base.rb +2 -0
  52. data/lib/active_record/callbacks.rb +12 -6
  53. data/lib/active_record/coders/json.rb +2 -0
  54. data/lib/active_record/coders/yaml_column.rb +15 -1
  55. data/lib/active_record/collection_cache_key.rb +12 -8
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +142 -42
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -0
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +174 -33
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +15 -5
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -32
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +2 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +14 -5
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +64 -6
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +31 -53
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +152 -81
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +66 -21
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +84 -97
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +110 -173
  69. data/lib/active_record/connection_adapters/column.rb +3 -1
  70. data/lib/active_record/connection_adapters/connection_specification.rb +17 -3
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +13 -2
  72. data/lib/active_record/connection_adapters/mysql/column.rb +2 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +47 -2
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +2 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +9 -10
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +5 -3
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +7 -10
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +30 -30
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +106 -1
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +2 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -2
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +2 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +2 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +13 -1
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +2 -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 +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +2 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -1
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -3
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +2 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +8 -2
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +4 -2
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +3 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +2 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +2 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +3 -1
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +18 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +19 -25
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +50 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +24 -11
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +20 -13
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +234 -112
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +3 -1
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +66 -74
  117. data/lib/active_record/connection_adapters/schema_cache.rb +4 -2
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +2 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +2 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +24 -1
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +2 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +6 -15
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +3 -2
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +75 -1
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +82 -95
  126. data/lib/active_record/connection_adapters/statement_pool.rb +2 -0
  127. data/lib/active_record/connection_handling.rb +4 -2
  128. data/lib/active_record/core.rb +51 -61
  129. data/lib/active_record/counter_cache.rb +20 -15
  130. data/lib/active_record/define_callbacks.rb +5 -3
  131. data/lib/active_record/dynamic_matchers.rb +9 -9
  132. data/lib/active_record/enum.rb +18 -13
  133. data/lib/active_record/errors.rb +60 -15
  134. data/lib/active_record/explain.rb +3 -1
  135. data/lib/active_record/explain_registry.rb +2 -0
  136. data/lib/active_record/explain_subscriber.rb +2 -0
  137. data/lib/active_record/fixture_set/file.rb +2 -0
  138. data/lib/active_record/fixtures.rb +67 -60
  139. data/lib/active_record/gem_version.rb +5 -3
  140. data/lib/active_record/inheritance.rb +49 -19
  141. data/lib/active_record/integration.rb +58 -19
  142. data/lib/active_record/internal_metadata.rb +2 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +3 -1
  144. data/lib/active_record/locking/optimistic.rb +30 -42
  145. data/lib/active_record/locking/pessimistic.rb +9 -6
  146. data/lib/active_record/log_subscriber.rb +43 -0
  147. data/lib/active_record/migration/command_recorder.rb +11 -9
  148. data/lib/active_record/migration/compatibility.rb +47 -9
  149. data/lib/active_record/migration/join_table.rb +2 -0
  150. data/lib/active_record/migration.rb +189 -139
  151. data/lib/active_record/model_schema.rb +19 -24
  152. data/lib/active_record/nested_attributes.rb +18 -6
  153. data/lib/active_record/no_touching.rb +3 -1
  154. data/lib/active_record/null_relation.rb +2 -0
  155. data/lib/active_record/persistence.rb +198 -49
  156. data/lib/active_record/query_cache.rb +12 -14
  157. data/lib/active_record/querying.rb +4 -2
  158. data/lib/active_record/railtie.rb +80 -6
  159. data/lib/active_record/railties/console_sandbox.rb +2 -0
  160. data/lib/active_record/railties/controller_runtime.rb +2 -0
  161. data/lib/active_record/railties/databases.rake +46 -36
  162. data/lib/active_record/readonly_attributes.rb +3 -2
  163. data/lib/active_record/reflection.rb +108 -194
  164. data/lib/active_record/relation/batches/batch_enumerator.rb +2 -0
  165. data/lib/active_record/relation/batches.rb +20 -5
  166. data/lib/active_record/relation/calculations.rb +46 -20
  167. data/lib/active_record/relation/delegation.rb +45 -27
  168. data/lib/active_record/relation/finder_methods.rb +77 -78
  169. data/lib/active_record/relation/from_clause.rb +2 -8
  170. data/lib/active_record/relation/merger.rb +53 -23
  171. data/lib/active_record/relation/predicate_builder/array_handler.rb +10 -7
  172. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  173. data/lib/active_record/relation/predicate_builder/base_handler.rb +2 -2
  174. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +12 -1
  175. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  176. data/lib/active_record/relation/predicate_builder/range_handler.rb +26 -9
  177. data/lib/active_record/relation/predicate_builder/relation_handler.rb +6 -0
  178. data/lib/active_record/relation/predicate_builder.rb +60 -79
  179. data/lib/active_record/relation/query_attribute.rb +28 -2
  180. data/lib/active_record/relation/query_methods.rb +129 -100
  181. data/lib/active_record/relation/record_fetch_warning.rb +2 -0
  182. data/lib/active_record/relation/spawn_methods.rb +4 -2
  183. data/lib/active_record/relation/where_clause.rb +65 -68
  184. data/lib/active_record/relation/where_clause_factory.rb +5 -48
  185. data/lib/active_record/relation.rb +120 -214
  186. data/lib/active_record/result.rb +2 -0
  187. data/lib/active_record/runtime_registry.rb +2 -0
  188. data/lib/active_record/sanitization.rb +129 -121
  189. data/lib/active_record/schema.rb +4 -2
  190. data/lib/active_record/schema_dumper.rb +36 -26
  191. data/lib/active_record/schema_migration.rb +2 -0
  192. data/lib/active_record/scoping/default.rb +8 -9
  193. data/lib/active_record/scoping/named.rb +23 -7
  194. data/lib/active_record/scoping.rb +9 -8
  195. data/lib/active_record/secure_token.rb +2 -0
  196. data/lib/active_record/serialization.rb +2 -0
  197. data/lib/active_record/statement_cache.rb +23 -13
  198. data/lib/active_record/store.rb +3 -1
  199. data/lib/active_record/suppressor.rb +2 -0
  200. data/lib/active_record/table_metadata.rb +12 -3
  201. data/lib/active_record/tasks/database_tasks.rb +26 -15
  202. data/lib/active_record/tasks/mysql_database_tasks.rb +9 -48
  203. data/lib/active_record/tasks/postgresql_database_tasks.rb +10 -2
  204. data/lib/active_record/tasks/sqlite_database_tasks.rb +25 -3
  205. data/lib/active_record/timestamp.rb +13 -6
  206. data/lib/active_record/touch_later.rb +2 -0
  207. data/lib/active_record/transactions.rb +33 -28
  208. data/lib/active_record/translation.rb +2 -0
  209. data/lib/active_record/type/adapter_specific_registry.rb +2 -0
  210. data/lib/active_record/type/date.rb +2 -0
  211. data/lib/active_record/type/date_time.rb +2 -0
  212. data/lib/active_record/type/decimal_without_scale.rb +2 -0
  213. data/lib/active_record/type/hash_lookup_type_map.rb +2 -0
  214. data/lib/active_record/type/internal/timezone.rb +2 -0
  215. data/lib/active_record/type/json.rb +30 -0
  216. data/lib/active_record/type/serialized.rb +6 -0
  217. data/lib/active_record/type/text.rb +2 -0
  218. data/lib/active_record/type/time.rb +2 -0
  219. data/lib/active_record/type/type_map.rb +2 -0
  220. data/lib/active_record/type/unsigned_integer.rb +2 -0
  221. data/lib/active_record/type.rb +4 -1
  222. data/lib/active_record/type_caster/connection.rb +2 -0
  223. data/lib/active_record/type_caster/map.rb +3 -1
  224. data/lib/active_record/type_caster.rb +2 -0
  225. data/lib/active_record/validations/absence.rb +2 -0
  226. data/lib/active_record/validations/associated.rb +2 -0
  227. data/lib/active_record/validations/length.rb +2 -0
  228. data/lib/active_record/validations/presence.rb +2 -0
  229. data/lib/active_record/validations/uniqueness.rb +36 -6
  230. data/lib/active_record/validations.rb +2 -0
  231. data/lib/active_record/version.rb +2 -0
  232. data/lib/active_record.rb +11 -4
  233. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  234. data/lib/rails/generators/active_record/{model/templates/application_record.rb → application_record/templates/application_record.rb.tt} +0 -0
  235. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -1
  236. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +0 -0
  237. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +0 -0
  238. data/lib/rails/generators/active_record/migration.rb +2 -0
  239. data/lib/rails/generators/active_record/model/model_generator.rb +2 -23
  240. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +0 -0
  241. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  242. data/lib/rails/generators/active_record.rb +3 -1
  243. metadata +26 -40
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -15
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -17
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -15
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -15
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -18
  251. data/lib/active_record/attribute/user_provided_default.rb +0 -30
  252. data/lib/active_record/attribute.rb +0 -240
  253. data/lib/active_record/attribute_mutation_tracker.rb +0 -114
  254. data/lib/active_record/attribute_set/builder.rb +0 -124
  255. data/lib/active_record/attribute_set/yaml_encoder.rb +0 -41
  256. data/lib/active_record/attribute_set.rb +0 -113
  257. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -10
  258. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  259. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +0 -88
  260. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +0 -59
  261. data/lib/active_record/type/internal/abstract_json.rb +0 -37
@@ -1,8 +1,9 @@
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_scope, :model, :klass
6
7
  attr_reader :preloaded_records
7
8
 
8
9
  def initialize(klass, owners, reflection, preload_scope)
@@ -11,79 +12,54 @@ module ActiveRecord
11
12
  @reflection = reflection
12
13
  @preload_scope = preload_scope
13
14
  @model = owners.first && owners.first.class
14
- @scope = nil
15
15
  @preloaded_records = []
16
16
  end
17
17
 
18
18
  def run(preloader)
19
- preload(preloader)
20
- end
21
-
22
- def preload(preloader)
23
- raise NotImplementedError
24
- end
25
-
26
- def scope
27
- @scope ||= build_scope
28
- end
29
-
30
- def records_for(ids)
31
- scope.where(association_key_name => ids)
32
- end
33
-
34
- def table
35
- klass.arel_table
36
- end
37
-
38
- # The name of the key on the associated records
39
- def association_key_name
40
- raise NotImplementedError
41
- end
42
-
43
- # This is overridden by HABTM as the condition should be on the foreign_key column in
44
- # the join table
45
- def association_key
46
- klass.arel_attribute(association_key_name, table)
47
- end
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)
23
+ end
48
24
 
49
- # The name of the key on the model which declares the association
50
- def owner_key_name
51
- raise NotImplementedError
25
+ owners.each do |owner|
26
+ associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || [])
27
+ end
52
28
  end
53
29
 
54
- def options
55
- reflection.options
56
- end
30
+ protected
31
+ attr_reader :owners, :reflection, :preload_scope, :model, :klass
57
32
 
58
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
59
38
 
60
- def associated_records_by_owner(preloader)
61
- records = load_records do |record|
62
- owner = owners_by_key[convert_key(record[association_key_name])]
63
- association = owner.association(reflection.name)
64
- association.set_inverse_instance(record)
65
- 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
66
43
 
67
- owners.each_with_object({}) do |owner, result|
68
- result[owner] = records[convert_key(owner[owner_key_name])] || []
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?
69
51
  end
70
52
  end
71
53
 
72
54
  def owner_keys
73
- unless defined?(@owner_keys)
74
- @owner_keys = owners.map do |owner|
75
- owner[owner_key_name]
76
- end
77
- @owner_keys.uniq!
78
- @owner_keys.compact!
79
- end
80
- @owner_keys
55
+ @owner_keys ||= owners_by_key.keys
81
56
  end
82
57
 
83
58
  def owners_by_key
84
59
  unless defined?(@owners_by_key)
85
60
  @owners_by_key = owners.each_with_object({}) do |owner, h|
86
- h[convert_key(owner[owner_key_name])] = owner
61
+ key = convert_key(owner[owner_key_name])
62
+ h[key] = owner if key
87
63
  end
88
64
  end
89
65
  @owners_by_key
@@ -106,11 +82,11 @@ module ActiveRecord
106
82
  end
107
83
 
108
84
  def association_key_type
109
- @klass.type_for_attribute(association_key_name.to_s).type
85
+ @klass.type_for_attribute(association_key_name).type
110
86
  end
111
87
 
112
88
  def owner_key_type
113
- @model.type_for_attribute(owner_key_name.to_s).type
89
+ @model.type_for_attribute(owner_key_name).type
114
90
  end
115
91
 
116
92
  def load_records(&block)
@@ -119,26 +95,34 @@ module ActiveRecord
119
95
  # Make several smaller queries if necessary or make one query if the adapter supports it
120
96
  slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
121
97
  @preloaded_records = slices.flat_map do |slice|
122
- records_for(slice).load(&block)
98
+ records_for(slice, &block)
123
99
  end
124
100
  @preloaded_records.group_by do |record|
125
101
  convert_key(record[association_key_name])
126
102
  end
127
103
  end
128
104
 
105
+ def records_for(ids, &block)
106
+ scope.where(association_key_name => ids).load(&block)
107
+ end
108
+
109
+ def scope
110
+ @scope ||= build_scope
111
+ end
112
+
129
113
  def reflection_scope
130
- @reflection_scope ||= reflection.scope_for(klass)
114
+ @reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped
131
115
  end
132
116
 
133
117
  def build_scope
134
118
  scope = klass.scope_for_association
135
119
 
136
120
  if reflection.type
137
- scope.where!(reflection.type => model.base_class.sti_name)
121
+ scope.where!(reflection.type => model.polymorphic_name)
138
122
  end
139
123
 
140
- scope.merge!(reflection_scope)
141
- scope.merge!(preload_scope) if preload_scope != NULL_RELATION
124
+ scope.merge!(reflection_scope) if reflection.scope
125
+ scope.merge!(preload_scope) if preload_scope
142
126
  scope
143
127
  end
144
128
  end
@@ -1,113 +1,105 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class Preloader
4
- module ThroughAssociation #:nodoc:
5
- def through_reflection
6
- reflection.through_reflection
7
- end
8
-
9
- def source_reflection
10
- reflection.source_reflection
11
- end
12
-
13
- def associated_records_by_owner(preloader)
14
- preloader.preload(owners,
15
- through_reflection.name,
16
- through_scope)
17
-
18
- through_records = owners.map do |owner|
19
- association = owner.association through_reflection.name
20
-
21
- center = target_records_from_association(association)
22
- [owner, Array(center)]
23
- end
24
-
25
- reset_association owners, through_reflection.name
26
-
27
- middle_records = through_records.flat_map { |(_, rec)| rec }
28
-
29
- preloaders = preloader.preload(middle_records,
30
- source_reflection.name,
31
- reflection_scope)
32
-
6
+ class ThroughAssociation < Association # :nodoc:
7
+ def run(preloader)
8
+ already_loaded = owners.first.association(through_reflection.name).loaded?
9
+ through_scope = through_scope()
10
+ reflection_scope = target_reflection_scope
11
+ through_preloaders = preloader.preload(owners, through_reflection.name, through_scope)
12
+ middle_records = through_preloaders.flat_map(&:preloaded_records)
13
+ preloaders = preloader.preload(middle_records, source_reflection.name, reflection_scope)
33
14
  @preloaded_records = preloaders.flat_map(&:preloaded_records)
34
15
 
35
- middle_to_pl = preloaders.each_with_object({}) do |pl, h|
36
- pl.owners.each { |middle|
37
- h[middle] = pl
38
- }
39
- end
40
-
41
- through_records.each_with_object({}) do |(lhs, center), records_by_owner|
42
- pl_to_middle = center.group_by { |record| middle_to_pl[record] }
43
-
44
- records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
45
- rhs_records = middles.flat_map { |r|
46
- association = r.association source_reflection.name
47
-
48
- target_records_from_association(association)
49
- }.compact
50
-
51
- # Respect the order on `reflection_scope` if it exists, else use the natural order.
52
- if reflection_scope.values[:order].present?
53
- @id_map ||= id_to_index_map @preloaded_records
54
- rhs_records.sort_by { |rhs| @id_map[rhs] }
55
- else
56
- rhs_records
16
+ owners.each do |owner|
17
+ through_records = Array(owner.association(through_reflection.name).target)
18
+ if already_loaded
19
+ if source_type = reflection.options[:source_type]
20
+ through_records = through_records.select do |record|
21
+ record[reflection.foreign_type] == source_type
22
+ end
57
23
  end
24
+ else
25
+ owner.association(through_reflection.name).reset if through_scope
26
+ end
27
+ result = through_records.flat_map do |record|
28
+ association = record.association(source_reflection.name)
29
+ target = association.target
30
+ association.reset if preload_scope
31
+ target
32
+ end
33
+ result.compact!
34
+ if reflection_scope
35
+ result.sort_by! { |rhs| preload_index[rhs] } if reflection_scope.order_values.any?
36
+ result.uniq! if reflection_scope.distinct_value
58
37
  end
38
+ associate_records_to_owner(owner, result)
59
39
  end
60
40
  end
61
41
 
62
42
  private
63
-
64
- def id_to_index_map(ids)
65
- id_map = {}
66
- ids.each_with_index { |id, index| id_map[id] = index }
67
- id_map
43
+ def through_reflection
44
+ reflection.through_reflection
68
45
  end
69
46
 
70
- def reset_association(owners, association_name)
71
- should_reset = (through_scope != through_reflection.klass.unscoped) ||
72
- (reflection.options[:source_type] && through_reflection.collection?)
47
+ def source_reflection
48
+ reflection.source_reflection
49
+ end
73
50
 
74
- # Don't cache the association - we would only be caching a subset
75
- if should_reset
76
- owners.each { |owner|
77
- owner.association(association_name).reset
78
- }
51
+ def preload_index
52
+ @preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index|
53
+ result[id] = index
79
54
  end
80
55
  end
81
56
 
82
57
  def through_scope
83
58
  scope = through_reflection.klass.unscoped
84
- values = reflection_scope.values
59
+ options = reflection.options
85
60
 
86
61
  if options[:source_type]
87
62
  scope.where! reflection.foreign_type => options[:source_type]
88
- else
89
- unless reflection_scope.where_clause.empty?
90
- scope.includes_values = Array(values[:includes] || options[:source])
91
- scope.where_clause = reflection_scope.where_clause
92
- if joins = values[:joins]
93
- scope.joins!(source_reflection.name => joins)
94
- end
95
- if left_outer_joins = values[:left_outer_joins]
96
- scope.left_outer_joins!(source_reflection.name => left_outer_joins)
97
- end
63
+ elsif !reflection_scope.where_clause.empty?
64
+ scope.where_clause = reflection_scope.where_clause
65
+ values = reflection_scope.values
66
+
67
+ if includes = values[:includes]
68
+ scope.includes!(source_reflection.name => includes)
69
+ else
70
+ scope.includes!(source_reflection.name)
71
+ end
72
+
73
+ if values[:references] && !values[:references].empty?
74
+ scope.references!(values[:references])
75
+ else
76
+ scope.references!(source_reflection.table_name)
77
+ end
78
+
79
+ if joins = values[:joins]
80
+ scope.joins!(source_reflection.name => joins)
81
+ end
82
+
83
+ if left_outer_joins = values[:left_outer_joins]
84
+ scope.left_outer_joins!(source_reflection.name => left_outer_joins)
98
85
  end
99
86
 
100
- scope.references! values[:references]
101
87
  if scope.eager_loading? && order_values = values[:order]
102
88
  scope = scope.order(order_values)
103
89
  end
104
90
  end
105
91
 
106
- scope
92
+ scope unless scope.empty_scope?
107
93
  end
108
94
 
109
- def target_records_from_association(association)
110
- association.loaded? ? association.target : association.reader
95
+ def target_reflection_scope
96
+ if preload_scope
97
+ reflection_scope.merge(preload_scope)
98
+ elsif reflection.scope
99
+ reflection_scope
100
+ else
101
+ nil
102
+ end
111
103
  end
112
104
  end
113
105
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  # Implements the details of eager loading of Active Record associations.
@@ -42,20 +44,10 @@ module ActiveRecord
42
44
  extend ActiveSupport::Autoload
43
45
 
44
46
  eager_autoload do
45
- autoload :Association, "active_record/associations/preloader/association"
46
- autoload :SingularAssociation, "active_record/associations/preloader/singular_association"
47
- autoload :CollectionAssociation, "active_record/associations/preloader/collection_association"
48
- autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
49
-
50
- autoload :HasMany, "active_record/associations/preloader/has_many"
51
- autoload :HasManyThrough, "active_record/associations/preloader/has_many_through"
52
- autoload :HasOne, "active_record/associations/preloader/has_one"
53
- autoload :HasOneThrough, "active_record/associations/preloader/has_one_through"
54
- autoload :BelongsTo, "active_record/associations/preloader/belongs_to"
47
+ autoload :Association, "active_record/associations/preloader/association"
48
+ autoload :ThroughAssociation, "active_record/associations/preloader/through_association"
55
49
  end
56
50
 
57
- NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
58
-
59
51
  # Eager loads the named associations for the given Active Record record(s).
60
52
  #
61
53
  # In this description, 'association name' shall refer to the name passed
@@ -91,14 +83,13 @@ module ActiveRecord
91
83
  # { author: :avatar }
92
84
  # [ :books, { author: :avatar } ]
93
85
  def preload(records, associations, preload_scope = nil)
94
- records = Array.wrap(records).compact.uniq
95
- associations = Array.wrap(associations)
96
- preload_scope = preload_scope || NULL_RELATION
86
+ records = Array.wrap(records).compact
97
87
 
98
88
  if records.empty?
99
89
  []
100
90
  else
101
- associations.flat_map { |association|
91
+ records.uniq!
92
+ Array.wrap(associations).flat_map { |association|
102
93
  preloaders_on association, records, preload_scope
103
94
  }
104
95
  end
@@ -147,7 +138,7 @@ module ActiveRecord
147
138
  def preloaders_for_one(association, records, scope)
148
139
  grouped_records(association, records).flat_map do |reflection, klasses|
149
140
  klasses.map do |rhs_klass, rs|
150
- loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
141
+ loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
151
142
  loader.run self
152
143
  loader
153
144
  end
@@ -159,6 +150,7 @@ module ActiveRecord
159
150
  records.each do |record|
160
151
  next unless record
161
152
  assoc = record.association(association)
153
+ next unless assoc.klass
162
154
  klasses = h[assoc.reflection] ||= {}
163
155
  (klasses[assoc.klass] ||= []) << record
164
156
  end
@@ -166,8 +158,6 @@ module ActiveRecord
166
158
  end
167
159
 
168
160
  class AlreadyLoaded # :nodoc:
169
- attr_reader :owners, :reflection
170
-
171
161
  def initialize(klass, owners, reflection, preload_scope)
172
162
  @owners = owners
173
163
  @reflection = reflection
@@ -178,34 +168,24 @@ module ActiveRecord
178
168
  def preloaded_records
179
169
  owners.flat_map { |owner| owner.association(reflection.name).target }
180
170
  end
181
- end
182
171
 
183
- class NullPreloader # :nodoc:
184
- def self.new(klass, owners, reflection, preload_scope); self; end
185
- def self.run(preloader); end
186
- def self.preloaded_records; []; end
187
- def self.owners; []; end
172
+ protected
173
+ attr_reader :owners, :reflection
188
174
  end
189
175
 
190
176
  # Returns a class containing the logic needed to load preload the data
191
- # and attach it to a relation. For example +Preloader::Association+ or
192
- # +Preloader::HasManyThrough+. The class returned implements a `run` method
177
+ # and attach it to a relation. The class returned implements a `run` method
193
178
  # that accepts a preloader.
194
- def preloader_for(reflection, owners, rhs_klass)
195
- return NullPreloader unless rhs_klass
196
-
197
- if owners.first.association(reflection.name).loaded?
179
+ def preloader_for(reflection, owners)
180
+ if owners.all? { |o| o.association(reflection.name).loaded? }
198
181
  return AlreadyLoaded
199
182
  end
200
183
  reflection.check_preloadable!
201
184
 
202
- case reflection.macro
203
- when :has_many
204
- reflection.options[:through] ? HasManyThrough : HasMany
205
- when :has_one
206
- reflection.options[:through] ? HasOneThrough : HasOne
207
- when :belongs_to
208
- BelongsTo
185
+ if reflection.options[:through]
186
+ ThroughAssociation
187
+ else
188
+ Association
209
189
  end
210
190
  end
211
191
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module Associations
3
5
  class SingularAssociation < Association #:nodoc:
@@ -15,9 +17,8 @@ module ActiveRecord
15
17
  replace(record)
16
18
  end
17
19
 
18
- def build(attributes = {})
19
- record = build_record(attributes)
20
- yield(record) if block_given?
20
+ def build(attributes = {}, &block)
21
+ record = build_record(attributes, &block)
21
22
  set_new_record(record)
22
23
  record
23
24
  end
@@ -30,24 +31,22 @@ module ActiveRecord
30
31
  end
31
32
 
32
33
  private
33
-
34
- def create_scope
35
- scope.scope_for_create.stringify_keys.except(klass.primary_key)
34
+ def scope_for_create
35
+ super.except!(klass.primary_key)
36
36
  end
37
37
 
38
38
  def find_target
39
- return scope.take if skip_statement_cache?
39
+ scope = self.scope
40
+ return scope.take if skip_statement_cache?(scope)
40
41
 
41
42
  conn = klass.connection
42
- sc = reflection.association_scope_cache(conn, owner) do
43
- StatementCache.create(conn) { |params|
44
- as = AssociationScope.create { params.bind }
45
- target_scope.merge(as.scope(self, conn)).limit(1)
46
- }
43
+ sc = reflection.association_scope_cache(conn, owner) do |params|
44
+ as = AssociationScope.create { params.bind }
45
+ target_scope.merge!(as.scope(self)).limit(1)
47
46
  end
48
47
 
49
48
  binds = AssociationScope.get_bind_values(owner, reflection.chain)
50
- sc.execute(binds, klass, conn) do |record|
49
+ sc.execute(binds, conn) do |record|
51
50
  set_inverse_instance record
52
51
  end.first
53
52
  rescue ::RangeError
@@ -62,9 +61,8 @@ module ActiveRecord
62
61
  replace(record)
63
62
  end
64
63
 
65
- def _create_record(attributes, raise_error = false)
66
- record = build_record(attributes)
67
- yield(record) if block_given?
64
+ def _create_record(attributes, raise_error = false, &block)
65
+ record = build_record(attributes, &block)
68
66
  saved = record.save
69
67
  set_new_record(record)
70
68
  raise RecordInvalid.new(record) if !saved && raise_error
@@ -1,10 +1,27 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
- # = Active Record Through Association
3
4
  module Associations
5
+ # = Active Record Through Association
4
6
  module ThroughAssociation #:nodoc:
5
- delegate :source_reflection, :through_reflection, to: :reflection
7
+ delegate :source_reflection, to: :reflection
6
8
 
7
9
  private
10
+ def through_reflection
11
+ @through_reflection ||= begin
12
+ refl = reflection.through_reflection
13
+
14
+ while refl.through_reflection?
15
+ refl = refl.through_reflection
16
+ end
17
+
18
+ refl
19
+ end
20
+ end
21
+
22
+ def through_association
23
+ @through_association ||= owner.association(through_reflection.name)
24
+ end
8
25
 
9
26
  # We merge in these scopes for two reasons:
10
27
  #
@@ -36,24 +53,22 @@ module ActiveRecord
36
53
  def construct_join_attributes(*records)
37
54
  ensure_mutable
38
55
 
39
- if source_reflection.association_primary_key(reflection.klass) == reflection.klass.primary_key
56
+ association_primary_key = source_reflection.association_primary_key(reflection.klass)
57
+
58
+ if association_primary_key == reflection.klass.primary_key && !options[:source_type]
40
59
  join_attributes = { source_reflection.name => records }
41
60
  else
42
61
  join_attributes = {
43
- source_reflection.foreign_key =>
44
- records.map { |record|
45
- record.send(source_reflection.association_primary_key(reflection.klass))
46
- }
62
+ source_reflection.foreign_key => records.map(&association_primary_key.to_sym)
47
63
  }
48
64
  end
49
65
 
50
66
  if options[:source_type]
51
- join_attributes[source_reflection.foreign_type] =
52
- records.map { |record| record.class.base_class.name }
67
+ join_attributes[source_reflection.foreign_type] = [ options[:source_type] ]
53
68
  end
54
69
 
55
70
  if records.count == 1
56
- Hash[join_attributes.map { |k, v| [k, v.first] }]
71
+ join_attributes.transform_values!(&:first)
57
72
  else
58
73
  join_attributes
59
74
  end
@@ -99,7 +114,7 @@ module ActiveRecord
99
114
  attributes[inverse.foreign_key] = target.id
100
115
  end
101
116
 
102
- super(attributes)
117
+ super
103
118
  end
104
119
  end
105
120
  end