activerecord 5.2.6 → 6.0.0

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 (268) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +609 -622
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +4 -2
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +4 -2
  7. data/lib/active_record/associations/association.rb +52 -19
  8. data/lib/active_record/associations/association_scope.rb +4 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  11. data/lib/active_record/associations/builder/association.rb +14 -18
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -52
  13. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  15. data/lib/active_record/associations/builder/has_many.rb +2 -0
  16. data/lib/active_record/associations/builder/has_one.rb +35 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  18. data/lib/active_record/associations/collection_association.rb +6 -21
  19. data/lib/active_record/associations/collection_proxy.rb +12 -15
  20. data/lib/active_record/associations/foreign_association.rb +7 -0
  21. data/lib/active_record/associations/has_many_association.rb +2 -10
  22. data/lib/active_record/associations/has_many_through_association.rb +14 -14
  23. data/lib/active_record/associations/has_one_association.rb +28 -30
  24. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  25. data/lib/active_record/associations/join_dependency/join_association.rb +9 -10
  26. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  27. data/lib/active_record/associations/join_dependency.rb +24 -28
  28. data/lib/active_record/associations/preloader/association.rb +38 -36
  29. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  30. data/lib/active_record/associations/preloader.rb +40 -32
  31. data/lib/active_record/associations/singular_association.rb +2 -16
  32. data/lib/active_record/associations.rb +19 -14
  33. data/lib/active_record/attribute_assignment.rb +7 -10
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  35. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  36. data/lib/active_record/attribute_methods/primary_key.rb +15 -22
  37. data/lib/active_record/attribute_methods/query.rb +2 -3
  38. data/lib/active_record/attribute_methods/read.rb +15 -53
  39. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  41. data/lib/active_record/attribute_methods/write.rb +17 -24
  42. data/lib/active_record/attribute_methods.rb +28 -100
  43. data/lib/active_record/attributes.rb +13 -0
  44. data/lib/active_record/autosave_association.rb +5 -9
  45. data/lib/active_record/base.rb +2 -3
  46. data/lib/active_record/callbacks.rb +5 -19
  47. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +94 -16
  48. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  49. data/lib/active_record/connection_adapters/abstract/database_statements.rb +95 -123
  50. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -8
  51. data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +19 -12
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +76 -48
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +132 -53
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +180 -47
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +128 -194
  59. data/lib/active_record/connection_adapters/column.rb +17 -13
  60. data/lib/active_record/connection_adapters/connection_specification.rb +52 -42
  61. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +73 -13
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  64. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  65. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +129 -13
  68. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -9
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +20 -1
  72. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  73. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  79. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  80. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  81. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +12 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +55 -53
  85. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +160 -74
  87. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  88. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -6
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +125 -141
  93. data/lib/active_record/connection_handling.rb +149 -27
  94. data/lib/active_record/core.rb +100 -60
  95. data/lib/active_record/counter_cache.rb +4 -29
  96. data/lib/active_record/database_configurations/database_config.rb +37 -0
  97. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  98. data/lib/active_record/database_configurations/url_config.rb +79 -0
  99. data/lib/active_record/database_configurations.rb +233 -0
  100. data/lib/active_record/dynamic_matchers.rb +1 -1
  101. data/lib/active_record/enum.rb +37 -7
  102. data/lib/active_record/errors.rb +15 -7
  103. data/lib/active_record/explain.rb +1 -1
  104. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  105. data/lib/active_record/fixture_set/render_context.rb +17 -0
  106. data/lib/active_record/fixture_set/table_row.rb +153 -0
  107. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  108. data/lib/active_record/fixtures.rb +145 -472
  109. data/lib/active_record/gem_version.rb +3 -3
  110. data/lib/active_record/inheritance.rb +13 -3
  111. data/lib/active_record/insert_all.rb +179 -0
  112. data/lib/active_record/integration.rb +68 -16
  113. data/lib/active_record/internal_metadata.rb +10 -2
  114. data/lib/active_record/locking/optimistic.rb +5 -6
  115. data/lib/active_record/locking/pessimistic.rb +3 -3
  116. data/lib/active_record/log_subscriber.rb +7 -26
  117. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  118. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  119. data/lib/active_record/middleware/database_selector.rb +75 -0
  120. data/lib/active_record/migration/command_recorder.rb +50 -6
  121. data/lib/active_record/migration/compatibility.rb +76 -49
  122. data/lib/active_record/migration.rb +100 -81
  123. data/lib/active_record/model_schema.rb +30 -9
  124. data/lib/active_record/nested_attributes.rb +2 -2
  125. data/lib/active_record/no_touching.rb +7 -0
  126. data/lib/active_record/persistence.rb +228 -24
  127. data/lib/active_record/query_cache.rb +11 -4
  128. data/lib/active_record/querying.rb +32 -20
  129. data/lib/active_record/railtie.rb +80 -43
  130. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  131. data/lib/active_record/railties/controller_runtime.rb +30 -35
  132. data/lib/active_record/railties/databases.rake +196 -46
  133. data/lib/active_record/reflection.rb +32 -30
  134. data/lib/active_record/relation/batches.rb +13 -10
  135. data/lib/active_record/relation/calculations.rb +53 -47
  136. data/lib/active_record/relation/delegation.rb +26 -43
  137. data/lib/active_record/relation/finder_methods.rb +13 -26
  138. data/lib/active_record/relation/merger.rb +11 -20
  139. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  140. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  141. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  142. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  143. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  144. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  145. data/lib/active_record/relation/predicate_builder.rb +4 -6
  146. data/lib/active_record/relation/query_attribute.rb +13 -8
  147. data/lib/active_record/relation/query_methods.rb +189 -63
  148. data/lib/active_record/relation/spawn_methods.rb +1 -1
  149. data/lib/active_record/relation/where_clause.rb +14 -10
  150. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  151. data/lib/active_record/relation.rb +310 -80
  152. data/lib/active_record/result.rb +30 -11
  153. data/lib/active_record/sanitization.rb +32 -40
  154. data/lib/active_record/schema.rb +2 -11
  155. data/lib/active_record/schema_dumper.rb +22 -7
  156. data/lib/active_record/schema_migration.rb +5 -1
  157. data/lib/active_record/scoping/default.rb +4 -5
  158. data/lib/active_record/scoping/named.rb +19 -15
  159. data/lib/active_record/scoping.rb +8 -8
  160. data/lib/active_record/statement_cache.rb +30 -3
  161. data/lib/active_record/store.rb +87 -8
  162. data/lib/active_record/table_metadata.rb +10 -17
  163. data/lib/active_record/tasks/database_tasks.rb +194 -25
  164. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -5
  165. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  166. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  167. data/lib/active_record/test_databases.rb +23 -0
  168. data/lib/active_record/test_fixtures.rb +224 -0
  169. data/lib/active_record/timestamp.rb +39 -25
  170. data/lib/active_record/touch_later.rb +4 -2
  171. data/lib/active_record/transactions.rb +57 -66
  172. data/lib/active_record/translation.rb +1 -1
  173. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  174. data/lib/active_record/type.rb +3 -4
  175. data/lib/active_record/type_caster/connection.rb +15 -14
  176. data/lib/active_record/type_caster/map.rb +1 -4
  177. data/lib/active_record/validations/uniqueness.rb +15 -27
  178. data/lib/active_record/validations.rb +1 -0
  179. data/lib/active_record.rb +9 -2
  180. data/lib/arel/alias_predication.rb +9 -0
  181. data/lib/arel/attributes/attribute.rb +37 -0
  182. data/lib/arel/attributes.rb +22 -0
  183. data/lib/arel/collectors/bind.rb +24 -0
  184. data/lib/arel/collectors/composite.rb +31 -0
  185. data/lib/arel/collectors/plain_string.rb +20 -0
  186. data/lib/arel/collectors/sql_string.rb +20 -0
  187. data/lib/arel/collectors/substitute_binds.rb +28 -0
  188. data/lib/arel/crud.rb +42 -0
  189. data/lib/arel/delete_manager.rb +18 -0
  190. data/lib/arel/errors.rb +9 -0
  191. data/lib/arel/expressions.rb +29 -0
  192. data/lib/arel/factory_methods.rb +49 -0
  193. data/lib/arel/insert_manager.rb +49 -0
  194. data/lib/arel/math.rb +45 -0
  195. data/lib/arel/nodes/and.rb +32 -0
  196. data/lib/arel/nodes/ascending.rb +23 -0
  197. data/lib/arel/nodes/binary.rb +52 -0
  198. data/lib/arel/nodes/bind_param.rb +36 -0
  199. data/lib/arel/nodes/case.rb +55 -0
  200. data/lib/arel/nodes/casted.rb +50 -0
  201. data/lib/arel/nodes/comment.rb +29 -0
  202. data/lib/arel/nodes/count.rb +12 -0
  203. data/lib/arel/nodes/delete_statement.rb +45 -0
  204. data/lib/arel/nodes/descending.rb +23 -0
  205. data/lib/arel/nodes/equality.rb +18 -0
  206. data/lib/arel/nodes/extract.rb +24 -0
  207. data/lib/arel/nodes/false.rb +16 -0
  208. data/lib/arel/nodes/full_outer_join.rb +8 -0
  209. data/lib/arel/nodes/function.rb +44 -0
  210. data/lib/arel/nodes/grouping.rb +8 -0
  211. data/lib/arel/nodes/in.rb +8 -0
  212. data/lib/arel/nodes/infix_operation.rb +80 -0
  213. data/lib/arel/nodes/inner_join.rb +8 -0
  214. data/lib/arel/nodes/insert_statement.rb +37 -0
  215. data/lib/arel/nodes/join_source.rb +20 -0
  216. data/lib/arel/nodes/matches.rb +18 -0
  217. data/lib/arel/nodes/named_function.rb +23 -0
  218. data/lib/arel/nodes/node.rb +50 -0
  219. data/lib/arel/nodes/node_expression.rb +13 -0
  220. data/lib/arel/nodes/outer_join.rb +8 -0
  221. data/lib/arel/nodes/over.rb +15 -0
  222. data/lib/arel/nodes/regexp.rb +16 -0
  223. data/lib/arel/nodes/right_outer_join.rb +8 -0
  224. data/lib/arel/nodes/select_core.rb +67 -0
  225. data/lib/arel/nodes/select_statement.rb +41 -0
  226. data/lib/arel/nodes/sql_literal.rb +16 -0
  227. data/lib/arel/nodes/string_join.rb +11 -0
  228. data/lib/arel/nodes/table_alias.rb +27 -0
  229. data/lib/arel/nodes/terminal.rb +16 -0
  230. data/lib/arel/nodes/true.rb +16 -0
  231. data/lib/arel/nodes/unary.rb +45 -0
  232. data/lib/arel/nodes/unary_operation.rb +20 -0
  233. data/lib/arel/nodes/unqualified_column.rb +22 -0
  234. data/lib/arel/nodes/update_statement.rb +41 -0
  235. data/lib/arel/nodes/values_list.rb +9 -0
  236. data/lib/arel/nodes/window.rb +126 -0
  237. data/lib/arel/nodes/with.rb +11 -0
  238. data/lib/arel/nodes.rb +68 -0
  239. data/lib/arel/order_predications.rb +13 -0
  240. data/lib/arel/predications.rb +257 -0
  241. data/lib/arel/select_manager.rb +271 -0
  242. data/lib/arel/table.rb +110 -0
  243. data/lib/arel/tree_manager.rb +72 -0
  244. data/lib/arel/update_manager.rb +34 -0
  245. data/lib/arel/visitors/depth_first.rb +204 -0
  246. data/lib/arel/visitors/dot.rb +297 -0
  247. data/lib/arel/visitors/ibm_db.rb +34 -0
  248. data/lib/arel/visitors/informix.rb +62 -0
  249. data/lib/arel/visitors/mssql.rb +157 -0
  250. data/lib/arel/visitors/mysql.rb +83 -0
  251. data/lib/arel/visitors/oracle.rb +159 -0
  252. data/lib/arel/visitors/oracle12.rb +66 -0
  253. data/lib/arel/visitors/postgresql.rb +110 -0
  254. data/lib/arel/visitors/sqlite.rb +39 -0
  255. data/lib/arel/visitors/to_sql.rb +889 -0
  256. data/lib/arel/visitors/visitor.rb +46 -0
  257. data/lib/arel/visitors/where_sql.rb +23 -0
  258. data/lib/arel/visitors.rb +20 -0
  259. data/lib/arel/window_predications.rb +9 -0
  260. data/lib/arel.rb +51 -0
  261. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  262. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  263. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  264. data/lib/rails/generators/active_record/migration.rb +14 -1
  265. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  266. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  267. metadata +108 -26
  268. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -7,7 +7,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
7
7
  end
8
8
 
9
9
  def self.valid_options(options)
10
- valid = super + [:as]
10
+ valid = super + [:as, :touch]
11
11
  valid += [:through, :source, :source_type] if options[:through]
12
12
  valid
13
13
  end
@@ -16,6 +16,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
16
16
  [:destroy, :delete, :nullify, :restrict_with_error, :restrict_with_exception]
17
17
  end
18
18
 
19
+ def self.define_callbacks(model, reflection)
20
+ super
21
+ add_touch_callbacks(model, reflection) if reflection.options[:touch]
22
+ end
23
+
19
24
  def self.add_destroy_callbacks(model, reflection)
20
25
  super unless reflection.options[:through]
21
26
  end
@@ -26,5 +31,34 @@ module ActiveRecord::Associations::Builder # :nodoc:
26
31
  model.validates_presence_of reflection.name, message: :required
27
32
  end
28
33
  end
34
+
35
+ def self.touch_record(o, name, touch)
36
+ record = o.send name
37
+
38
+ return unless record && record.persisted?
39
+
40
+ if touch != true
41
+ record.touch(touch)
42
+ else
43
+ record.touch
44
+ end
45
+ end
46
+
47
+ def self.add_touch_callbacks(model, reflection)
48
+ name = reflection.name
49
+ touch = reflection.options[:touch]
50
+
51
+ callback = lambda { |record|
52
+ HasOne.touch_record(record, name, touch)
53
+ }
54
+
55
+ model.after_create callback, if: :saved_changes?
56
+ model.after_update callback, if: :saved_changes?
57
+ model.after_destroy callback
58
+ model.after_touch callback
59
+ end
60
+
61
+ private_class_method :macro, :valid_options, :valid_dependent_options, :add_destroy_callbacks,
62
+ :define_callbacks, :define_validations, :add_touch_callbacks
29
63
  end
30
64
  end
@@ -38,5 +38,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
38
38
  end
39
39
  CODE
40
40
  end
41
+
42
+ private_class_method :valid_options, :define_accessors, :define_constructors
41
43
  end
42
44
  end
@@ -211,9 +211,11 @@ module ActiveRecord
211
211
  def size
212
212
  if !find_target? || loaded?
213
213
  target.size
214
+ elsif @association_ids
215
+ @association_ids.size
214
216
  elsif !association_scope.group_values.empty?
215
217
  load_target.size
216
- elsif !association_scope.distinct_value && target.is_a?(Array)
218
+ elsif !association_scope.distinct_value && !target.empty?
217
219
  unsaved_records = target.select(&:new_record?)
218
220
  unsaved_records.size + count_records
219
221
  else
@@ -230,10 +232,10 @@ module ActiveRecord
230
232
  # loaded and you are going to fetch the records anyway it is better to
231
233
  # check <tt>collection.length.zero?</tt>.
232
234
  def empty?
233
- if loaded?
235
+ if loaded? || @association_ids || reflection.has_cached_counter?
234
236
  size.zero?
235
237
  else
236
- @target.blank? && !scope.exists?
238
+ target.empty? && !scope.exists?
237
239
  end
238
240
  end
239
241
 
@@ -300,23 +302,6 @@ module ActiveRecord
300
302
  end
301
303
 
302
304
  private
303
-
304
- def find_target
305
- scope = self.scope
306
- return scope.to_a if skip_statement_cache?(scope)
307
-
308
- conn = klass.connection
309
- sc = reflection.association_scope_cache(conn, owner) do |params|
310
- as = AssociationScope.create { params.bind }
311
- target_scope.merge!(as.scope(self))
312
- end
313
-
314
- binds = AssociationScope.get_bind_values(owner, reflection.chain)
315
- sc.execute(binds, conn) do |record|
316
- set_inverse_instance(record)
317
- end
318
- end
319
-
320
305
  # We have some records loaded from the database (persisted) and some that are
321
306
  # in-memory (memory). The same record may be represented in the persisted array
322
307
  # and in the memory array.
@@ -396,7 +381,7 @@ module ActiveRecord
396
381
  records.each { |record| callback(:before_remove, record) }
397
382
 
398
383
  delete_records(existing_records, method) if existing_records.any?
399
- records.each { |record| target.delete(record) }
384
+ @target -= records
400
385
  @association_ids = nil
401
386
 
402
387
  records.each { |record| callback(:after_remove, record) }
@@ -2,11 +2,8 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Associations
5
- # Association proxies in Active Record are middlemen between the object that
6
- # holds the association, known as the <tt>@owner</tt>, and the actual associated
7
- # object, known as the <tt>@target</tt>. The kind of association any proxy is
8
- # about is available in <tt>@reflection</tt>. That's an instance of the class
9
- # ActiveRecord::Reflection::AssociationReflection.
5
+ # Collection proxies in Active Record are middlemen between an
6
+ # <tt>association</tt>, and its <tt>target</tt> result set.
10
7
  #
11
8
  # For example, given
12
9
  #
@@ -16,14 +13,14 @@ module ActiveRecord
16
13
  #
17
14
  # blog = Blog.first
18
15
  #
19
- # the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
20
- # <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
21
- # the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
16
+ # The collection proxy returned by <tt>blog.posts</tt> is built from a
17
+ # <tt>:has_many</tt> <tt>association</tt>, and delegates to a collection
18
+ # of posts as the <tt>target</tt>.
22
19
  #
23
- # This class delegates unknown methods to <tt>@target</tt> via
24
- # <tt>method_missing</tt>.
20
+ # This class delegates unknown methods to the <tt>association</tt>'s
21
+ # relation class via a delegate cache.
25
22
  #
26
- # The <tt>@target</tt> object is not \loaded until needed. For example,
23
+ # The <tt>target</tt> result set is not loaded until needed. For example,
27
24
  #
28
25
  # blog.posts.count
29
26
  #
@@ -1005,7 +1002,7 @@ module ActiveRecord
1005
1002
  end
1006
1003
 
1007
1004
  # Adds one or more +records+ to the collection by setting their foreign keys
1008
- # to the association's primary key. Since +<<+ flattens its argument list and
1005
+ # to the association's primary key. Since <tt><<</tt> flattens its argument list and
1009
1006
  # inserts each record, +push+ and +concat+ behave identically. Returns +self+
1010
1007
  # so several appends may be chained together.
1011
1008
  #
@@ -1032,7 +1029,7 @@ module ActiveRecord
1032
1029
  alias_method :append, :<<
1033
1030
  alias_method :concat, :<<
1034
1031
 
1035
- def prepend(*args)
1032
+ def prepend(*args) # :nodoc:
1036
1033
  raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
1037
1034
  end
1038
1035
 
@@ -1062,7 +1059,7 @@ module ActiveRecord
1062
1059
  # person.pets.reload # fetches pets from the database
1063
1060
  # # => [#<Pet id: 1, name: "Snoop", group: "dogs", person_id: 1>]
1064
1061
  def reload
1065
- proxy_association.reload
1062
+ proxy_association.reload(true)
1066
1063
  reset_scope
1067
1064
  end
1068
1065
 
@@ -1099,7 +1096,7 @@ module ActiveRecord
1099
1096
  SpawnMethods,
1100
1097
  ].flat_map { |klass|
1101
1098
  klass.public_instance_methods(false)
1102
- } - self.public_instance_methods(false) - [:select] + [:scoping]
1099
+ } - self.public_instance_methods(false) - [:select] + [:scoping, :values]
1103
1100
 
1104
1101
  delegate(*delegate_methods, to: :scope)
1105
1102
 
@@ -9,5 +9,12 @@ module ActiveRecord::Associations
9
9
  false
10
10
  end
11
11
  end
12
+
13
+ def nullified_owner_attributes
14
+ Hash.new.tap do |attrs|
15
+ attrs[reflection.foreign_key] = nil
16
+ attrs[reflection.type] = nil if reflection.type.present?
17
+ end
18
+ end
12
19
  end
13
20
  end
@@ -36,14 +36,6 @@ module ActiveRecord
36
36
  super
37
37
  end
38
38
 
39
- def empty?
40
- if reflection.has_cached_counter?
41
- size.zero?
42
- else
43
- super
44
- end
45
- end
46
-
47
39
  private
48
40
 
49
41
  # Returns the number of records in this collection.
@@ -69,7 +61,7 @@ module ActiveRecord
69
61
  # If there's nothing in the database and @target has no new records
70
62
  # we are certain the current target is an empty array. This is a
71
63
  # documented side-effect of the method that may avoid an extra SELECT.
72
- (@target ||= []) && loaded! if count == 0
64
+ loaded! if count == 0
73
65
 
74
66
  [association_scope.limit_value, count].compact.min
75
67
  end
@@ -92,7 +84,7 @@ module ActiveRecord
92
84
  if method == :delete_all
93
85
  scope.delete_all
94
86
  else
95
- scope.update_all(reflection.foreign_key => nil)
87
+ scope.update_all(nullified_owner_attributes)
96
88
  end
97
89
  end
98
90
 
@@ -21,20 +21,6 @@ module ActiveRecord
21
21
  super
22
22
  end
23
23
 
24
- def concat_records(records)
25
- ensure_not_nested
26
-
27
- records = super(records, true)
28
-
29
- if owner.new_record? && records
30
- records.flatten.each do |record|
31
- build_through_record(record)
32
- end
33
- end
34
-
35
- records
36
- end
37
-
38
24
  def insert_record(record, validate = true, raise = false)
39
25
  ensure_not_nested
40
26
 
@@ -48,6 +34,20 @@ module ActiveRecord
48
34
  end
49
35
 
50
36
  private
37
+ def concat_records(records)
38
+ ensure_not_nested
39
+
40
+ records = super(records, true)
41
+
42
+ if owner.new_record? && records
43
+ records.flatten.each do |record|
44
+ build_through_record(record)
45
+ end
46
+ end
47
+
48
+ records
49
+ end
50
+
51
51
  # The through record (built with build_record) is temporarily cached
52
52
  # so that it may be reused if insert_record is subsequently called.
53
53
  #
@@ -23,35 +23,6 @@ module ActiveRecord
23
23
  end
24
24
  end
25
25
 
26
- def replace(record, save = true)
27
- raise_on_type_mismatch!(record) if record
28
- load_target
29
-
30
- return target unless target || record
31
-
32
- assigning_another_record = target != record
33
- if assigning_another_record || record.has_changes_to_save?
34
- save &&= owner.persisted?
35
-
36
- transaction_if(save) do
37
- remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
38
-
39
- if record
40
- set_owner_attributes(record)
41
- set_inverse_instance(record)
42
-
43
- if save && !record.save
44
- nullify_owner_attributes(record)
45
- set_owner_attributes(target) if target
46
- raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
47
- end
48
- end
49
- end
50
- end
51
-
52
- self.target = record
53
- end
54
-
55
26
  def delete(method = options[:dependent])
56
27
  if load_target
57
28
  case method
@@ -62,12 +33,39 @@ module ActiveRecord
62
33
  target.destroy
63
34
  throw(:abort) unless target.destroyed?
64
35
  when :nullify
65
- target.update_columns(reflection.foreign_key => nil) if target.persisted?
36
+ target.update_columns(nullified_owner_attributes) if target.persisted?
66
37
  end
67
38
  end
68
39
  end
69
40
 
70
41
  private
42
+ def replace(record, save = true)
43
+ raise_on_type_mismatch!(record) if record
44
+
45
+ return target unless load_target || record
46
+
47
+ assigning_another_record = target != record
48
+ if assigning_another_record || record.has_changes_to_save?
49
+ save &&= owner.persisted?
50
+
51
+ transaction_if(save) do
52
+ remove_target!(options[:dependent]) if target && !target.destroyed? && assigning_another_record
53
+
54
+ if record
55
+ set_owner_attributes(record)
56
+ set_inverse_instance(record)
57
+
58
+ if save && !record.save
59
+ nullify_owner_attributes(record)
60
+ set_owner_attributes(target) if target
61
+ raise RecordNotSaved, "Failed to save the new associated #{reflection.name}."
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ self.target = record
68
+ end
71
69
 
72
70
  # The reason that the save param for replace is false, if for create (not just build),
73
71
  # is because the setting of the foreign keys is actually handled by the scoping when
@@ -6,12 +6,12 @@ module ActiveRecord
6
6
  class HasOneThroughAssociation < HasOneAssociation #:nodoc:
7
7
  include ThroughAssociation
8
8
 
9
- def replace(record, save = true)
10
- create_through_record(record, save)
11
- self.target = record
12
- end
13
-
14
9
  private
10
+ def replace(record, save = true)
11
+ create_through_record(record, save)
12
+ self.target = record
13
+ end
14
+
15
15
  def create_through_record(record, save)
16
16
  ensure_not_nested
17
17
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record/associations/join_dependency/join_part"
4
+ require "active_support/core_ext/array/extract"
4
5
 
5
6
  module ActiveRecord
6
7
  module Associations
@@ -35,10 +36,9 @@ module ActiveRecord
35
36
  arel = join_scope.arel(alias_tracker.aliases)
36
37
  nodes = arel.constraints.first
37
38
 
38
- others, children = nodes.children.partition do |node|
39
- !fetch_arel_attribute(node) { |attr| attr.relation.name == table.name }
39
+ others = nodes.children.extract! do |node|
40
+ Arel.fetch_attribute(node) { |attr| attr.relation.name != table.name }
40
41
  end
41
- nodes = table.create_and(children)
42
42
 
43
43
  joins << table.create_join(table, table.create_on(nodes), join_type)
44
44
 
@@ -59,14 +59,13 @@ module ActiveRecord
59
59
  @table = tables.first
60
60
  end
61
61
 
62
- private
63
- def fetch_arel_attribute(value)
64
- case value
65
- when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual
66
- yield value.left.is_a?(Arel::Attributes::Attribute) ? value.left : value.right
67
- end
68
- end
62
+ def readonly?
63
+ return @readonly if defined?(@readonly)
69
64
 
65
+ @readonly = reflection.scope && reflection.scope_for(base_klass.unscoped).readonly_value
66
+ end
67
+
68
+ private
70
69
  def append_constraints(join, constraints)
71
70
  if join.is_a?(Arel::Nodes::StringJoin)
72
71
  join_string = table.create_and(constraints.unshift(join.left))
@@ -54,8 +54,8 @@ module ActiveRecord
54
54
  length = column_names_with_alias.length
55
55
 
56
56
  while index < length
57
- column_name, alias_name = column_names_with_alias[index]
58
- hash[column_name] = row[alias_name]
57
+ column = column_names_with_alias[index]
58
+ hash[column.name] = row[column.alias]
59
59
  index += 1
60
60
  end
61
61
 
@@ -14,10 +14,8 @@ module ActiveRecord
14
14
  i[column.name] = column.alias
15
15
  }
16
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
- }
17
+ @columns_cache = tables.each_with_object({}) { |table, h|
18
+ h[table.node] = table.columns
21
19
  }
22
20
  end
23
21
 
@@ -25,9 +23,8 @@ module ActiveRecord
25
23
  @tables.flat_map(&:column_aliases)
26
24
  end
27
25
 
28
- # An array of [column_name, alias] pairs for the table
29
26
  def column_aliases(node)
30
- @name_and_alias_cache[node]
27
+ @columns_cache[node]
31
28
  end
32
29
 
33
30
  def column_alias(node, column)
@@ -67,16 +64,17 @@ module ActiveRecord
67
64
  end
68
65
  end
69
66
 
70
- def initialize(base, table, associations)
67
+ def initialize(base, table, associations, join_type)
71
68
  tree = self.class.make_tree associations
72
69
  @join_root = JoinBase.new(base, table, build(tree, base))
70
+ @join_type = join_type
73
71
  end
74
72
 
75
73
  def reflections
76
74
  join_root.drop(1).map!(&:reflection)
77
75
  end
78
76
 
79
- def join_constraints(joins_to_add, join_type, alias_tracker)
77
+ def join_constraints(joins_to_add, alias_tracker)
80
78
  @alias_tracker = alias_tracker
81
79
 
82
80
  construct_tables!(join_root)
@@ -85,9 +83,9 @@ module ActiveRecord
85
83
  joins.concat joins_to_add.flat_map { |oj|
86
84
  construct_tables!(oj.join_root)
87
85
  if join_root.match? oj.join_root
88
- walk join_root, oj.join_root
86
+ walk(join_root, oj.join_root, oj.join_type)
89
87
  else
90
- make_join_constraints(oj.join_root, join_type)
88
+ make_join_constraints(oj.join_root, oj.join_type)
91
89
  end
92
90
  }
93
91
  end
@@ -116,7 +114,7 @@ module ActiveRecord
116
114
  result_set.each { |row_hash|
117
115
  parent_key = primary_key ? row_hash[primary_key] : row_hash
118
116
  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)
117
+ construct(parent, join_root, row_hash, seen, model_cache)
120
118
  }
121
119
  end
122
120
 
@@ -128,9 +126,11 @@ module ActiveRecord
128
126
  end
129
127
 
130
128
  protected
131
- attr_reader :alias_tracker, :join_root
129
+ attr_reader :join_root, :join_type
132
130
 
133
131
  private
132
+ attr_reader :alias_tracker
133
+
134
134
  def aliases
135
135
  @aliases ||= Aliases.new join_root.each_with_index.map { |join_part, i|
136
136
  columns = join_part.column_names.each_with_index.map { |column_name, j|
@@ -152,7 +152,7 @@ module ActiveRecord
152
152
  end
153
153
  end
154
154
 
155
- def make_constraints(parent, child, join_type = Arel::Nodes::OuterJoin)
155
+ def make_constraints(parent, child, join_type)
156
156
  foreign_table = parent.table
157
157
  foreign_klass = parent.base_klass
158
158
  joins = child.join_constraints(foreign_table, foreign_klass, join_type, alias_tracker)
@@ -170,17 +170,17 @@ module ActiveRecord
170
170
  end
171
171
 
172
172
  def table_alias_for(reflection, parent, join)
173
- name = "#{reflection.plural_name}_#{parent.table_name}"
173
+ name = reflection.alias_candidate(parent.table_name)
174
174
  join ? "#{name}_join" : name
175
175
  end
176
176
 
177
- def walk(left, right)
177
+ def walk(left, right, join_type)
178
178
  intersection, missing = right.children.map { |node1|
179
179
  [left.children.find { |node2| node1.match? node2 }, node1]
180
180
  }.partition(&:first)
181
181
 
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) }
182
+ joins = intersection.flat_map { |l, r| r.table = l.table; walk(l, r, join_type) }
183
+ joins.concat missing.flat_map { |_, n| make_constraints(left, n, join_type) }
184
184
  end
185
185
 
186
186
  def find_reflection(klass, name)
@@ -202,7 +202,7 @@ module ActiveRecord
202
202
  end
203
203
  end
204
204
 
205
- def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
205
+ def construct(ar_parent, parent, row, seen, model_cache)
206
206
  return if ar_parent.nil?
207
207
 
208
208
  parent.children.each do |node|
@@ -211,7 +211,7 @@ module ActiveRecord
211
211
  other.loaded!
212
212
  elsif ar_parent.association_cached?(node.reflection.name)
213
213
  model = ar_parent.association(node.reflection.name).target
214
- construct(model, node, row, rs, seen, model_cache, aliases)
214
+ construct(model, node, row, seen, model_cache)
215
215
  next
216
216
  end
217
217
 
@@ -226,22 +226,17 @@ module ActiveRecord
226
226
  model = seen[ar_parent.object_id][node][id]
227
227
 
228
228
  if model
229
- construct(model, node, row, rs, seen, model_cache, aliases)
229
+ construct(model, node, row, seen, model_cache)
230
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
231
+ model = construct_model(ar_parent, node, row, model_cache, id)
237
232
 
238
233
  seen[ar_parent.object_id][node][id] = model
239
- construct(model, node, row, rs, seen, model_cache, aliases)
234
+ construct(model, node, row, seen, model_cache)
240
235
  end
241
236
  end
242
237
  end
243
238
 
244
- def construct_model(record, node, row, model_cache, id, aliases)
239
+ def construct_model(record, node, row, model_cache, id)
245
240
  other = record.association(node.reflection.name)
246
241
 
247
242
  model = model_cache[node][id] ||=
@@ -255,6 +250,7 @@ module ActiveRecord
255
250
  other.target = model
256
251
  end
257
252
 
253
+ model.readonly! if node.readonly?
258
254
  model
259
255
  end
260
256
  end