activerecord 6.0.3.4 → 6.1.2

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 (245) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +891 -695
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record.rb +7 -14
  6. data/lib/active_record/aggregations.rb +5 -5
  7. data/lib/active_record/association_relation.rb +30 -12
  8. data/lib/active_record/associations.rb +118 -11
  9. data/lib/active_record/associations/alias_tracker.rb +19 -15
  10. data/lib/active_record/associations/association.rb +44 -28
  11. data/lib/active_record/associations/association_scope.rb +19 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +22 -8
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
  14. data/lib/active_record/associations/builder/association.rb +32 -5
  15. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  16. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  18. data/lib/active_record/associations/builder/has_many.rb +6 -2
  19. data/lib/active_record/associations/builder/has_one.rb +11 -14
  20. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  21. data/lib/active_record/associations/collection_association.rb +19 -6
  22. data/lib/active_record/associations/collection_proxy.rb +13 -5
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -2
  25. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  26. data/lib/active_record/associations/has_one_association.rb +15 -1
  27. data/lib/active_record/associations/join_dependency.rb +72 -50
  28. data/lib/active_record/associations/join_dependency/join_association.rb +39 -16
  29. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  30. data/lib/active_record/associations/preloader.rb +11 -5
  31. data/lib/active_record/associations/preloader/association.rb +51 -25
  32. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  33. data/lib/active_record/associations/singular_association.rb +1 -1
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/attribute_assignment.rb +10 -8
  36. data/lib/active_record/attribute_methods.rb +64 -54
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  38. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  39. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  40. data/lib/active_record/attribute_methods/query.rb +3 -6
  41. data/lib/active_record/attribute_methods/read.rb +8 -11
  42. data/lib/active_record/attribute_methods/serialization.rb +11 -5
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  44. data/lib/active_record/attribute_methods/write.rb +12 -20
  45. data/lib/active_record/attributes.rb +33 -8
  46. data/lib/active_record/autosave_association.rb +57 -40
  47. data/lib/active_record/base.rb +2 -14
  48. data/lib/active_record/callbacks.rb +152 -22
  49. data/lib/active_record/coders/yaml_column.rb +1 -1
  50. data/lib/active_record/connection_adapters.rb +50 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +191 -134
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +116 -27
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +228 -83
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +80 -32
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +54 -72
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -96
  64. data/lib/active_record/connection_adapters/column.rb +15 -1
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -25
  68. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  70. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
  71. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  72. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
  73. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +11 -7
  74. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  76. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  77. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  79. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +13 -54
  80. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  82. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  83. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  90. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  91. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  93. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  94. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -58
  96. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  97. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +31 -6
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  101. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +37 -4
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +49 -50
  103. data/lib/active_record/connection_handling.rb +218 -71
  104. data/lib/active_record/core.rb +245 -61
  105. data/lib/active_record/database_configurations.rb +124 -85
  106. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  107. data/lib/active_record/database_configurations/database_config.rb +52 -9
  108. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  109. data/lib/active_record/database_configurations/url_config.rb +15 -40
  110. data/lib/active_record/delegated_type.rb +209 -0
  111. data/lib/active_record/destroy_association_async_job.rb +36 -0
  112. data/lib/active_record/enum.rb +82 -38
  113. data/lib/active_record/errors.rb +47 -12
  114. data/lib/active_record/explain.rb +9 -4
  115. data/lib/active_record/explain_subscriber.rb +1 -1
  116. data/lib/active_record/fixture_set/file.rb +10 -17
  117. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  118. data/lib/active_record/fixture_set/render_context.rb +1 -1
  119. data/lib/active_record/fixture_set/table_row.rb +2 -2
  120. data/lib/active_record/fixtures.rb +58 -9
  121. data/lib/active_record/gem_version.rb +3 -3
  122. data/lib/active_record/inheritance.rb +40 -18
  123. data/lib/active_record/insert_all.rb +35 -6
  124. data/lib/active_record/integration.rb +3 -5
  125. data/lib/active_record/internal_metadata.rb +16 -7
  126. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  127. data/lib/active_record/locking/optimistic.rb +33 -17
  128. data/lib/active_record/locking/pessimistic.rb +6 -2
  129. data/lib/active_record/log_subscriber.rb +27 -8
  130. data/lib/active_record/middleware/database_selector.rb +4 -1
  131. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  132. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  133. data/lib/active_record/migration.rb +113 -83
  134. data/lib/active_record/migration/command_recorder.rb +47 -27
  135. data/lib/active_record/migration/compatibility.rb +68 -17
  136. data/lib/active_record/model_schema.rb +117 -13
  137. data/lib/active_record/nested_attributes.rb +2 -3
  138. data/lib/active_record/no_touching.rb +1 -1
  139. data/lib/active_record/persistence.rb +50 -45
  140. data/lib/active_record/query_cache.rb +15 -5
  141. data/lib/active_record/querying.rb +11 -6
  142. data/lib/active_record/railtie.rb +64 -44
  143. data/lib/active_record/railties/console_sandbox.rb +2 -4
  144. data/lib/active_record/railties/databases.rake +276 -99
  145. data/lib/active_record/readonly_attributes.rb +4 -0
  146. data/lib/active_record/reflection.rb +71 -57
  147. data/lib/active_record/relation.rb +96 -67
  148. data/lib/active_record/relation/batches.rb +38 -31
  149. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  150. data/lib/active_record/relation/calculations.rb +101 -44
  151. data/lib/active_record/relation/delegation.rb +2 -1
  152. data/lib/active_record/relation/finder_methods.rb +45 -15
  153. data/lib/active_record/relation/from_clause.rb +1 -1
  154. data/lib/active_record/relation/merger.rb +27 -25
  155. data/lib/active_record/relation/predicate_builder.rb +59 -38
  156. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  157. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  158. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  159. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  160. data/lib/active_record/relation/query_methods.rb +333 -195
  161. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  162. data/lib/active_record/relation/spawn_methods.rb +8 -7
  163. data/lib/active_record/relation/where_clause.rb +104 -57
  164. data/lib/active_record/result.rb +41 -33
  165. data/lib/active_record/runtime_registry.rb +2 -2
  166. data/lib/active_record/sanitization.rb +6 -17
  167. data/lib/active_record/schema_dumper.rb +34 -4
  168. data/lib/active_record/schema_migration.rb +2 -8
  169. data/lib/active_record/scoping/named.rb +6 -17
  170. data/lib/active_record/secure_token.rb +16 -8
  171. data/lib/active_record/serialization.rb +5 -3
  172. data/lib/active_record/signed_id.rb +116 -0
  173. data/lib/active_record/statement_cache.rb +20 -4
  174. data/lib/active_record/store.rb +2 -2
  175. data/lib/active_record/suppressor.rb +2 -2
  176. data/lib/active_record/table_metadata.rb +42 -51
  177. data/lib/active_record/tasks/database_tasks.rb +140 -113
  178. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  179. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  180. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  181. data/lib/active_record/test_databases.rb +5 -4
  182. data/lib/active_record/test_fixtures.rb +37 -16
  183. data/lib/active_record/timestamp.rb +4 -6
  184. data/lib/active_record/touch_later.rb +21 -21
  185. data/lib/active_record/transactions.rb +19 -66
  186. data/lib/active_record/type.rb +8 -1
  187. data/lib/active_record/type/serialized.rb +6 -2
  188. data/lib/active_record/type/time.rb +10 -0
  189. data/lib/active_record/type_caster/connection.rb +0 -1
  190. data/lib/active_record/type_caster/map.rb +8 -5
  191. data/lib/active_record/validations.rb +1 -0
  192. data/lib/active_record/validations/numericality.rb +35 -0
  193. data/lib/active_record/validations/uniqueness.rb +24 -4
  194. data/lib/arel.rb +5 -13
  195. data/lib/arel/attributes/attribute.rb +4 -0
  196. data/lib/arel/collectors/bind.rb +5 -0
  197. data/lib/arel/collectors/composite.rb +8 -0
  198. data/lib/arel/collectors/sql_string.rb +7 -0
  199. data/lib/arel/collectors/substitute_binds.rb +7 -0
  200. data/lib/arel/nodes.rb +3 -1
  201. data/lib/arel/nodes/binary.rb +82 -8
  202. data/lib/arel/nodes/bind_param.rb +8 -0
  203. data/lib/arel/nodes/casted.rb +21 -9
  204. data/lib/arel/nodes/equality.rb +6 -9
  205. data/lib/arel/nodes/grouping.rb +3 -0
  206. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  207. data/lib/arel/nodes/in.rb +8 -1
  208. data/lib/arel/nodes/infix_operation.rb +13 -1
  209. data/lib/arel/nodes/join_source.rb +1 -1
  210. data/lib/arel/nodes/node.rb +7 -6
  211. data/lib/arel/nodes/ordering.rb +27 -0
  212. data/lib/arel/nodes/sql_literal.rb +3 -0
  213. data/lib/arel/nodes/table_alias.rb +7 -3
  214. data/lib/arel/nodes/unary.rb +0 -1
  215. data/lib/arel/predications.rb +12 -18
  216. data/lib/arel/select_manager.rb +1 -2
  217. data/lib/arel/table.rb +13 -5
  218. data/lib/arel/visitors.rb +0 -7
  219. data/lib/arel/visitors/dot.rb +14 -2
  220. data/lib/arel/visitors/mysql.rb +11 -1
  221. data/lib/arel/visitors/postgresql.rb +15 -4
  222. data/lib/arel/visitors/to_sql.rb +89 -78
  223. data/lib/rails/generators/active_record/migration.rb +6 -1
  224. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  225. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  226. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  227. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  228. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  229. metadata +25 -26
  230. data/lib/active_record/advisory_lock_base.rb +0 -18
  231. data/lib/active_record/attribute_decorators.rb +0 -88
  232. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  233. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  234. data/lib/active_record/define_callbacks.rb +0 -22
  235. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  236. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  237. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  238. data/lib/arel/attributes.rb +0 -22
  239. data/lib/arel/visitors/depth_first.rb +0 -203
  240. data/lib/arel/visitors/ibm_db.rb +0 -34
  241. data/lib/arel/visitors/informix.rb +0 -62
  242. data/lib/arel/visitors/mssql.rb +0 -156
  243. data/lib/arel/visitors/oracle.rb +0 -158
  244. data/lib/arel/visitors/oracle12.rb +0 -65
  245. data/lib/arel/visitors/where_sql.rb +0 -22
@@ -114,6 +114,8 @@ module ActiveRecord
114
114
  # Person.first(3) # returns the first three objects fetched by SELECT * FROM people ORDER BY people.id LIMIT 3
115
115
  #
116
116
  def first(limit = nil)
117
+ check_reorder_deprecation unless loaded?
118
+
117
119
  if limit
118
120
  find_nth_with_limit(0, limit)
119
121
  else
@@ -275,9 +277,9 @@ module ActiveRecord
275
277
  # * Integer - Finds the record with this primary key.
276
278
  # * String - Finds the record with a primary key corresponding to this
277
279
  # string (such as <tt>'5'</tt>).
278
- # * Array - Finds the record that matches these +find+-style conditions
280
+ # * Array - Finds the record that matches these +where+-style conditions
279
281
  # (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
280
- # * Hash - Finds the record that matches these +find+-style conditions
282
+ # * Hash - Finds the record that matches these +where+-style conditions
281
283
  # (such as <tt>{name: 'David'}</tt>).
282
284
  # * +false+ - Returns always +false+.
283
285
  # * No args - Returns +false+ if the relation is empty, +true+ otherwise.
@@ -313,10 +315,26 @@ module ActiveRecord
313
315
  end
314
316
 
315
317
  relation = construct_relation_for_exists(conditions)
318
+ return false if relation.where_clause.contradiction?
319
+
320
+ skip_query_cache_if_necessary { connection.select_rows(relation.arel, "#{name} Exists?").size == 1 }
321
+ end
316
322
 
317
- skip_query_cache_if_necessary { connection.select_one(relation.arel, "#{name} Exists?") } ? true : false
323
+ # Returns true if the relation contains the given record or false otherwise.
324
+ #
325
+ # No query is performed if the relation is loaded; the given record is
326
+ # compared to the records in memory. If the relation is unloaded, an
327
+ # efficient existence query is performed, as in #exists?.
328
+ def include?(record)
329
+ if loaded? || offset_value || limit_value
330
+ records.include?(record)
331
+ else
332
+ record.is_a?(klass) && exists?(record.id)
333
+ end
318
334
  end
319
335
 
336
+ alias :member? :include?
337
+
320
338
  # This method is called whenever no records are found with either a single
321
339
  # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
322
340
  #
@@ -326,15 +344,15 @@ module ActiveRecord
326
344
  # the expected number of results should be provided in the +expected_size+
327
345
  # argument.
328
346
  def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key, not_found_ids = nil) # :nodoc:
329
- conditions = arel.where_sql(@klass)
330
- conditions = " [#{conditions}]" if conditions
347
+ conditions = " [#{arel.where_sql(klass)}]" unless where_clause.empty?
348
+
331
349
  name = @klass.name
332
350
 
333
351
  if ids.nil?
334
352
  error = +"Couldn't find #{name}"
335
353
  error << " with#{conditions}" if conditions
336
354
  raise RecordNotFound.new(error, name, key)
337
- elsif Array(ids).size == 1
355
+ elsif Array.wrap(ids).size == 1
338
356
  error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
339
357
  raise RecordNotFound.new(error, name, key, ids)
340
358
  else
@@ -346,8 +364,15 @@ module ActiveRecord
346
364
  end
347
365
 
348
366
  private
349
- def offset_index
350
- offset_value || 0
367
+ def check_reorder_deprecation
368
+ if !order_values.empty? && order_values.all?(&:blank?)
369
+ blank_value = order_values.first
370
+ ActiveSupport::Deprecation.warn(<<~MSG.squish)
371
+ `.reorder(#{blank_value.inspect})` with `.first` / `.first!` no longer
372
+ takes non-deterministic result in Rails 6.2.
373
+ To continue taking non-deterministic result, use `.take` / `.take!` instead.
374
+ MSG
375
+ end
351
376
  end
352
377
 
353
378
  def construct_relation_for_exists(conditions)
@@ -371,7 +396,7 @@ module ActiveRecord
371
396
 
372
397
  def apply_join_dependency(eager_loading: group_values.empty?)
373
398
  join_dependency = construct_join_dependency(
374
- eager_load_values + includes_values, Arel::Nodes::OuterJoin
399
+ eager_load_values | includes_values, Arel::Nodes::OuterJoin
375
400
  )
376
401
  relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
377
402
 
@@ -401,14 +426,14 @@ module ActiveRecord
401
426
 
402
427
  def limited_ids_for(relation)
403
428
  values = @klass.connection.columns_for_distinct(
404
- connection.visitor.compile(arel_attribute(primary_key)),
429
+ connection.visitor.compile(table[primary_key]),
405
430
  relation.order_values
406
431
  )
407
432
 
408
433
  relation = relation.except(:select).select(values).distinct!
409
434
 
410
- id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL") }
411
- id_rows.map { |row| row[primary_key] }
435
+ id_rows = skip_query_cache_if_necessary { @klass.connection.select_rows(relation.arel, "SQL") }
436
+ id_rows.map(&:last)
412
437
  end
413
438
 
414
439
  def using_limitable_reflections?(reflections)
@@ -509,7 +534,8 @@ module ActiveRecord
509
534
  end
510
535
 
511
536
  def find_nth(index)
512
- @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first
537
+ @offsets ||= {}
538
+ @offsets[index] ||= find_nth_with_limit(index, 1).first
513
539
  end
514
540
 
515
541
  def find_nth_with_limit(index, limit)
@@ -523,7 +549,7 @@ module ActiveRecord
523
549
  end
524
550
 
525
551
  if limit > 0
526
- relation = relation.offset(offset_index + index) unless index.zero?
552
+ relation = relation.offset((offset_value || 0) + index) unless index.zero?
527
553
  relation.limit(limit).to_a
528
554
  else
529
555
  []
@@ -551,7 +577,11 @@ module ActiveRecord
551
577
 
552
578
  def ordered_relation
553
579
  if order_values.empty? && (implicit_order_column || primary_key)
554
- order(arel_attribute(implicit_order_column || primary_key).asc)
580
+ if implicit_order_column && primary_key && implicit_order_column != primary_key
581
+ order(table[implicit_order_column].asc, table[primary_key].asc)
582
+ else
583
+ order(table[implicit_order_column || primary_key].asc)
584
+ end
555
585
  else
556
586
  self
557
587
  end
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  end
24
24
 
25
25
  def self.empty
26
- @empty ||= new(nil, nil)
26
+ @empty ||= new(nil, nil).freeze
27
27
  end
28
28
  end
29
29
  end
@@ -7,15 +7,16 @@ module ActiveRecord
7
7
  class HashMerger # :nodoc:
8
8
  attr_reader :relation, :hash
9
9
 
10
- def initialize(relation, hash)
10
+ def initialize(relation, hash, rewhere = nil)
11
11
  hash.assert_valid_keys(*Relation::VALUE_METHODS)
12
12
 
13
13
  @relation = relation
14
14
  @hash = hash
15
+ @rewhere = rewhere
15
16
  end
16
17
 
17
- def merge #:nodoc:
18
- Merger.new(relation, other).merge
18
+ def merge
19
+ Merger.new(relation, other, @rewhere).merge
19
20
  end
20
21
 
21
22
  # Applying values to a relation has some side effects. E.g.
@@ -28,19 +29,14 @@ module ActiveRecord
28
29
  table: relation.table,
29
30
  predicate_builder: relation.predicate_builder
30
31
  )
31
- hash.each { |k, v|
32
- if k == :joins
33
- if Hash === v
34
- other.joins!(v)
35
- else
36
- other.joins!(*v)
37
- end
38
- elsif k == :select
39
- other._select!(v)
32
+ hash.each do |k, v|
33
+ k = :_select if k == :select
34
+ if Array === v
35
+ other.public_send("#{k}!", *v)
40
36
  else
41
- other.send("#{k}!", v)
37
+ other.public_send("#{k}!", v)
42
38
  end
43
- }
39
+ end
44
40
  other
45
41
  end
46
42
  end
@@ -48,10 +44,11 @@ module ActiveRecord
48
44
  class Merger # :nodoc:
49
45
  attr_reader :relation, :values, :other
50
46
 
51
- def initialize(relation, other)
47
+ def initialize(relation, other, rewhere = nil)
52
48
  @relation = relation
53
49
  @values = other.values
54
50
  @other = other
51
+ @rewhere = rewhere
55
52
  end
56
53
 
57
54
  NORMAL_VALUES = Relation::VALUE_METHODS -
@@ -73,7 +70,7 @@ module ActiveRecord
73
70
  if name == :select
74
71
  relation._select!(*value)
75
72
  else
76
- relation.send("#{name}!", *value)
73
+ relation.public_send("#{name}!", *value)
77
74
  end
78
75
  end
79
76
  end
@@ -93,8 +90,8 @@ module ActiveRecord
93
90
  return if other.preload_values.empty? && other.includes_values.empty?
94
91
 
95
92
  if other.klass == relation.klass
96
- relation.preload!(*other.preload_values) unless other.preload_values.empty?
97
- relation.includes!(other.includes_values) unless other.includes_values.empty?
93
+ relation.preload_values |= other.preload_values unless other.preload_values.empty?
94
+ relation.includes_values |= other.includes_values unless other.includes_values.empty?
98
95
  else
99
96
  reflection = relation.klass.reflect_on_all_associations.find do |r|
100
97
  r.class_name == other.klass.name
@@ -111,10 +108,10 @@ module ActiveRecord
111
108
  end
112
109
 
113
110
  def merge_joins
114
- return if other.joins_values.blank?
111
+ return if other.joins_values.empty?
115
112
 
116
113
  if other.klass == relation.klass
117
- relation.joins!(*other.joins_values)
114
+ relation.joins_values |= other.joins_values
118
115
  else
119
116
  associations, others = other.joins_values.partition do |join|
120
117
  case join
@@ -130,16 +127,21 @@ module ActiveRecord
130
127
  end
131
128
 
132
129
  def merge_outer_joins
133
- return if other.left_outer_joins_values.blank?
130
+ return if other.left_outer_joins_values.empty?
134
131
 
135
132
  if other.klass == relation.klass
136
- relation.left_outer_joins!(*other.left_outer_joins_values)
133
+ relation.left_outer_joins_values |= other.left_outer_joins_values
137
134
  else
138
- associations = other.left_outer_joins_values
135
+ associations, others = other.left_outer_joins_values.partition do |join|
136
+ case join
137
+ when Hash, Symbol, Array; true
138
+ end
139
+ end
140
+
139
141
  join_dependency = other.construct_join_dependency(
140
142
  associations, Arel::Nodes::OuterJoin
141
143
  )
142
- relation.joins!(join_dependency)
144
+ relation.left_outer_joins!(join_dependency, *others)
143
145
  end
144
146
  end
145
147
 
@@ -167,7 +169,7 @@ module ActiveRecord
167
169
  def merge_clauses
168
170
  relation.from_clause = other.from_clause if replace_from_clause?
169
171
 
170
- where_clause = relation.where_clause.merge(other.where_clause)
172
+ where_clause = relation.where_clause.merge(other.where_clause, @rewhere)
171
173
  relation.where_clause = where_clause unless where_clause.empty?
172
174
 
173
175
  having_clause = relation.having_clause.merge(other.having_clause)
@@ -2,34 +2,41 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class PredicateBuilder # :nodoc:
5
- delegate :resolve_column_aliases, to: :table
5
+ require "active_record/relation/predicate_builder/array_handler"
6
+ require "active_record/relation/predicate_builder/basic_object_handler"
7
+ require "active_record/relation/predicate_builder/range_handler"
8
+ require "active_record/relation/predicate_builder/relation_handler"
9
+ require "active_record/relation/predicate_builder/association_query_value"
10
+ require "active_record/relation/predicate_builder/polymorphic_array_value"
11
+
12
+ # No-op BaseHandler to work Mashal.load(File.read("legacy_relation.dump")).
13
+ # TODO: Remove the constant alias once Rails 6.1 has released.
14
+ BaseHandler = BasicObjectHandler
6
15
 
7
16
  def initialize(table)
8
17
  @table = table
9
18
  @handlers = []
10
19
 
11
20
  register_handler(BasicObject, BasicObjectHandler.new(self))
12
- register_handler(Base, BaseHandler.new(self))
13
21
  register_handler(Range, RangeHandler.new(self))
14
22
  register_handler(Relation, RelationHandler.new)
15
23
  register_handler(Array, ArrayHandler.new(self))
16
24
  register_handler(Set, ArrayHandler.new(self))
17
25
  end
18
26
 
19
- def build_from_hash(attributes)
27
+ def build_from_hash(attributes, &block)
20
28
  attributes = convert_dot_notation_to_hash(attributes)
21
- expand_from_hash(attributes)
29
+ expand_from_hash(attributes, &block)
22
30
  end
23
31
 
24
32
  def self.references(attributes)
25
- attributes.map do |key, value|
33
+ attributes.each_with_object([]) do |(key, value), result|
26
34
  if value.is_a?(Hash)
27
- key
28
- else
29
- key = key.to_s
30
- key.split(".").first if key.include?(".")
35
+ result << Arel.sql(key)
36
+ elsif key.include?(".")
37
+ result << Arel.sql(key.split(".").first)
31
38
  end
32
- end.compact
39
+ end
33
40
  end
34
41
 
35
42
  # Define how a class is converted to Arel nodes when passed to +where+.
@@ -47,27 +54,37 @@ module ActiveRecord
47
54
  @handlers.unshift([klass, handler])
48
55
  end
49
56
 
50
- def build(attribute, value)
51
- if table.type(attribute.name).force_equality?(value)
57
+ def [](attr_name, value, operator = nil)
58
+ build(table.arel_table[attr_name], value, operator)
59
+ end
60
+
61
+ def build(attribute, value, operator = nil)
62
+ value = value.id if value.respond_to?(:id)
63
+ if operator ||= table.type(attribute.name).force_equality?(value) && :eq
52
64
  bind = build_bind_attribute(attribute.name, value)
53
- attribute.eq(bind)
65
+ attribute.public_send(operator, bind)
54
66
  else
55
67
  handler_for(value).call(attribute, value)
56
68
  end
57
69
  end
58
70
 
59
71
  def build_bind_attribute(column_name, value)
60
- attr = Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
72
+ attr = Relation::QueryAttribute.new(column_name, value, table.type(column_name))
61
73
  Arel::Nodes::BindParam.new(attr)
62
74
  end
63
75
 
76
+ def resolve_arel_attribute(table_name, column_name, &block)
77
+ table.associated_table(table_name, &block).arel_table[column_name]
78
+ end
79
+
64
80
  protected
65
- def expand_from_hash(attributes)
81
+ def expand_from_hash(attributes, &block)
66
82
  return ["1=0"] if attributes.empty?
67
83
 
68
84
  attributes.flat_map do |key, value|
69
85
  if value.is_a?(Hash) && !table.has_column?(key)
70
- table.associated_predicate_builder(key).expand_from_hash(value)
86
+ table.associated_table(key, &block)
87
+ .predicate_builder.expand_from_hash(value.stringify_keys)
71
88
  elsif table.associated_with?(key)
72
89
  # Find the foreign key when using queries such as:
73
90
  # Post.where(author: author)
@@ -76,18 +93,20 @@ module ActiveRecord
76
93
  # PriceEstimate.where(estimate_of: treasure)
77
94
  associated_table = table.associated_table(key)
78
95
  if associated_table.polymorphic_association?
79
- case value.is_a?(Array) ? value.first : value
80
- when Base, Relation
81
- value = [value] unless value.is_a?(Array)
82
- klass = PolymorphicArrayValue
83
- end
96
+ value = [value] unless value.is_a?(Array)
97
+ klass = PolymorphicArrayValue
98
+ elsif associated_table.through_association?
99
+ next associated_table.predicate_builder.expand_from_hash(
100
+ associated_table.primary_key => value
101
+ )
84
102
  end
85
103
 
86
104
  klass ||= AssociationQueryValue
87
- queries = klass.new(associated_table, value).queries.map do |query|
88
- expand_from_hash(query).reduce(&:and)
105
+ queries = klass.new(associated_table, value).queries.map! do |query|
106
+ expand_from_hash(query)
89
107
  end
90
- queries.reduce(&:or)
108
+
109
+ grouping_queries(queries)
91
110
  elsif table.aggregated_with?(key)
92
111
  mapping = table.reflect_on_aggregation(key).mapping
93
112
  values = value.nil? ? [nil] : Array.wrap(value)
@@ -96,17 +115,18 @@ module ActiveRecord
96
115
  values = values.map do |object|
97
116
  object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
98
117
  end
99
- build(table.arel_attribute(column_name), values)
118
+ self[column_name, values]
100
119
  else
101
120
  queries = values.map do |object|
102
121
  mapping.map do |field_attr, aggregate_attr|
103
- build(table.arel_attribute(field_attr), object.try!(aggregate_attr))
104
- end.reduce(&:and)
122
+ self[field_attr, object.try!(aggregate_attr)]
123
+ end
105
124
  end
106
- queries.reduce(&:or)
125
+
126
+ grouping_queries(queries)
107
127
  end
108
128
  else
109
- build(table.arel_attribute(key), value)
129
+ self[key, value]
110
130
  end
111
131
  end
112
132
  end
@@ -114,6 +134,16 @@ module ActiveRecord
114
134
  private
115
135
  attr_reader :table
116
136
 
137
+ def grouping_queries(queries)
138
+ if queries.one?
139
+ queries.first
140
+ else
141
+ queries.map! { |query| query.reduce(&:and) }
142
+ queries = queries.reduce { |result, query| Arel::Nodes::Or.new(result, query) }
143
+ Arel::Nodes::Grouping.new(queries)
144
+ end
145
+ end
146
+
117
147
  def convert_dot_notation_to_hash(attributes)
118
148
  dot_notation = attributes.select do |k, v|
119
149
  k.include?(".") && !v.is_a?(Hash)
@@ -135,12 +165,3 @@ module ActiveRecord
135
165
  end
136
166
  end
137
167
  end
138
-
139
- require "active_record/relation/predicate_builder/array_handler"
140
- require "active_record/relation/predicate_builder/base_handler"
141
- require "active_record/relation/predicate_builder/basic_object_handler"
142
- require "active_record/relation/predicate_builder/range_handler"
143
- require "active_record/relation/predicate_builder/relation_handler"
144
-
145
- require "active_record/relation/predicate_builder/association_query_value"
146
- require "active_record/relation/predicate_builder/polymorphic_array_value"