activerecord 6.0.0.beta2 → 6.0.2.rc1

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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +471 -9
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +0 -1
  5. data/lib/active_record/association_relation.rb +15 -6
  6. data/lib/active_record/associations.rb +4 -3
  7. data/lib/active_record/associations/association.rb +10 -2
  8. data/lib/active_record/associations/builder/association.rb +14 -18
  9. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  10. data/lib/active_record/associations/builder/collection_association.rb +5 -15
  11. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  12. data/lib/active_record/associations/builder/has_many.rb +2 -0
  13. data/lib/active_record/associations/builder/has_one.rb +35 -1
  14. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  15. data/lib/active_record/associations/collection_association.rb +6 -2
  16. data/lib/active_record/associations/collection_proxy.rb +2 -2
  17. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  18. data/lib/active_record/associations/join_dependency.rb +14 -9
  19. data/lib/active_record/associations/join_dependency/join_association.rb +12 -3
  20. data/lib/active_record/associations/preloader.rb +13 -8
  21. data/lib/active_record/associations/preloader/association.rb +34 -30
  22. data/lib/active_record/associations/preloader/through_association.rb +48 -28
  23. data/lib/active_record/attribute_methods.rb +3 -53
  24. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  25. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  26. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  27. data/lib/active_record/attribute_methods/query.rb +2 -3
  28. data/lib/active_record/attribute_methods/read.rb +3 -9
  29. data/lib/active_record/attribute_methods/write.rb +6 -12
  30. data/lib/active_record/attributes.rb +13 -0
  31. data/lib/active_record/autosave_association.rb +21 -7
  32. data/lib/active_record/base.rb +0 -1
  33. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +109 -11
  34. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  35. data/lib/active_record/connection_adapters/abstract/database_statements.rb +88 -61
  36. data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -4
  37. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  38. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
  39. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  40. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +79 -22
  41. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -4
  42. data/lib/active_record/connection_adapters/abstract_adapter.rb +114 -34
  43. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +78 -73
  44. data/lib/active_record/connection_adapters/column.rb +17 -13
  45. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  46. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +2 -2
  47. data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
  48. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  49. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -5
  50. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +10 -7
  51. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  52. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  53. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  54. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
  55. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  56. data/lib/active_record/connection_adapters/postgresql/quoting.rb +39 -2
  57. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
  58. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  59. data/lib/active_record/connection_adapters/postgresql_adapter.rb +68 -27
  60. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  61. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  62. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  63. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  64. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
  65. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -114
  66. data/lib/active_record/connection_handling.rb +31 -13
  67. data/lib/active_record/core.rb +23 -24
  68. data/lib/active_record/database_configurations.rb +73 -44
  69. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  70. data/lib/active_record/database_configurations/url_config.rb +12 -12
  71. data/lib/active_record/dynamic_matchers.rb +1 -1
  72. data/lib/active_record/enum.rb +15 -0
  73. data/lib/active_record/errors.rb +1 -1
  74. data/lib/active_record/fixtures.rb +11 -6
  75. data/lib/active_record/gem_version.rb +2 -2
  76. data/lib/active_record/insert_all.rb +179 -0
  77. data/lib/active_record/integration.rb +13 -1
  78. data/lib/active_record/internal_metadata.rb +5 -1
  79. data/lib/active_record/locking/optimistic.rb +3 -4
  80. data/lib/active_record/log_subscriber.rb +1 -1
  81. data/lib/active_record/middleware/database_selector.rb +3 -3
  82. data/lib/active_record/middleware/database_selector/resolver.rb +4 -6
  83. data/lib/active_record/migration.rb +62 -44
  84. data/lib/active_record/migration/command_recorder.rb +28 -14
  85. data/lib/active_record/migration/compatibility.rb +10 -0
  86. data/lib/active_record/model_schema.rb +3 -0
  87. data/lib/active_record/persistence.rb +206 -13
  88. data/lib/active_record/querying.rb +17 -12
  89. data/lib/active_record/railtie.rb +0 -1
  90. data/lib/active_record/railties/databases.rake +127 -25
  91. data/lib/active_record/reflection.rb +3 -3
  92. data/lib/active_record/relation.rb +99 -20
  93. data/lib/active_record/relation/calculations.rb +38 -40
  94. data/lib/active_record/relation/delegation.rb +22 -30
  95. data/lib/active_record/relation/finder_methods.rb +17 -12
  96. data/lib/active_record/relation/merger.rb +11 -16
  97. data/lib/active_record/relation/query_methods.rb +228 -76
  98. data/lib/active_record/relation/where_clause.rb +9 -5
  99. data/lib/active_record/sanitization.rb +33 -4
  100. data/lib/active_record/schema.rb +1 -1
  101. data/lib/active_record/schema_dumper.rb +10 -1
  102. data/lib/active_record/schema_migration.rb +1 -1
  103. data/lib/active_record/scoping/default.rb +6 -7
  104. data/lib/active_record/scoping/named.rb +3 -2
  105. data/lib/active_record/statement_cache.rb +2 -2
  106. data/lib/active_record/store.rb +48 -0
  107. data/lib/active_record/table_metadata.rb +9 -13
  108. data/lib/active_record/tasks/database_tasks.rb +109 -6
  109. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  110. data/lib/active_record/test_databases.rb +1 -16
  111. data/lib/active_record/test_fixtures.rb +1 -0
  112. data/lib/active_record/timestamp.rb +26 -16
  113. data/lib/active_record/touch_later.rb +4 -2
  114. data/lib/active_record/transactions.rb +56 -46
  115. data/lib/active_record/type_caster/connection.rb +16 -10
  116. data/lib/active_record/validations.rb +1 -0
  117. data/lib/active_record/validations/uniqueness.rb +3 -5
  118. data/lib/arel.rb +12 -5
  119. data/lib/arel/insert_manager.rb +3 -3
  120. data/lib/arel/nodes.rb +2 -1
  121. data/lib/arel/nodes/comment.rb +29 -0
  122. data/lib/arel/nodes/select_core.rb +16 -12
  123. data/lib/arel/nodes/unary.rb +1 -0
  124. data/lib/arel/nodes/values_list.rb +2 -17
  125. data/lib/arel/select_manager.rb +10 -10
  126. data/lib/arel/visitors/depth_first.rb +7 -2
  127. data/lib/arel/visitors/dot.rb +7 -2
  128. data/lib/arel/visitors/ibm_db.rb +13 -0
  129. data/lib/arel/visitors/informix.rb +6 -0
  130. data/lib/arel/visitors/mssql.rb +15 -1
  131. data/lib/arel/visitors/oracle12.rb +4 -5
  132. data/lib/arel/visitors/postgresql.rb +4 -10
  133. data/lib/arel/visitors/to_sql.rb +107 -131
  134. data/lib/arel/visitors/visitor.rb +9 -5
  135. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  136. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  137. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  138. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  139. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  140. metadata +16 -12
  141. data/lib/active_record/collection_cache_key.rb +0 -53
  142. data/lib/arel/nodes/values.rb +0 -16
@@ -129,11 +129,12 @@ module ActiveRecord
129
129
  relation = apply_join_dependency
130
130
 
131
131
  if operation.to_s.downcase == "count"
132
- relation.distinct!
133
- # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
134
- if (column_name == :all || column_name.nil?) && select_values.empty?
135
- relation.order_values = []
132
+ unless distinct_value || distinct_select?(column_name || select_for_count)
133
+ relation.distinct!
134
+ relation.select_values = [ klass.primary_key || table[Arel.star] ]
136
135
  end
136
+ # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
137
+ relation.order_values = []
137
138
  end
138
139
 
139
140
  relation.calculate(operation, column_name)
@@ -221,7 +222,6 @@ module ActiveRecord
221
222
  end
222
223
 
223
224
  private
224
-
225
225
  def has_include?(column_name)
226
226
  eager_loading? || (includes_values.present? && column_name && column_name != :all)
227
227
  end
@@ -236,10 +236,12 @@ module ActiveRecord
236
236
  if operation == "count"
237
237
  column_name ||= select_for_count
238
238
  if column_name == :all
239
- if distinct && (group_values.any? || select_values.empty? && order_values.empty?)
239
+ if !distinct
240
+ distinct = distinct_select?(select_for_count) if group_values.empty?
241
+ elsif group_values.any? || select_values.empty? && order_values.empty?
240
242
  column_name = primary_key
241
243
  end
242
- elsif column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
244
+ elsif distinct_select?(column_name)
243
245
  distinct = nil
244
246
  end
245
247
  end
@@ -251,13 +253,15 @@ module ActiveRecord
251
253
  end
252
254
  end
253
255
 
256
+ def distinct_select?(column_name)
257
+ column_name.is_a?(::String) && /\bDISTINCT[\s(]/i.match?(column_name)
258
+ end
259
+
254
260
  def aggregate_column(column_name)
255
261
  return column_name if Arel::Expressions === column_name
256
262
 
257
- if @klass.has_attribute?(column_name) || @klass.attribute_alias?(column_name)
258
- @klass.arel_attribute(column_name)
259
- else
260
- Arel.sql(column_name == :all ? "*" : column_name.to_s)
263
+ arel_column(column_name.to_s) do |name|
264
+ Arel.sql(column_name == :all ? "*" : name)
261
265
  end
262
266
  end
263
267
 
@@ -302,25 +306,22 @@ module ActiveRecord
302
306
  end
303
307
 
304
308
  def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
305
- group_attrs = group_values
309
+ group_fields = group_values
306
310
 
307
- if group_attrs.first.respond_to?(:to_sym)
308
- association = @klass._reflect_on_association(group_attrs.first)
309
- associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
310
- group_fields = Array(associated ? association.foreign_key : group_attrs)
311
- else
312
- group_fields = group_attrs
311
+ if group_fields.size == 1 && group_fields.first.respond_to?(:to_sym)
312
+ association = klass._reflect_on_association(group_fields.first)
313
+ associated = association && association.belongs_to? # only count belongs_to associations
314
+ group_fields = Array(association.foreign_key) if associated
313
315
  end
314
316
  group_fields = arel_columns(group_fields)
315
317
 
316
- group_aliases = group_fields.map { |field| column_alias_for(field) }
318
+ group_aliases = group_fields.map { |field|
319
+ field = connection.visitor.compile(field) if Arel.arel_node?(field)
320
+ column_alias_for(field.to_s.downcase)
321
+ }
317
322
  group_columns = group_aliases.zip(group_fields)
318
323
 
319
- if operation == "count" && column_name == :all
320
- aggregate_alias = "count_all"
321
- else
322
- aggregate_alias = column_alias_for([operation, column_name].join(" "))
323
- end
324
+ aggregate_alias = column_alias_for("#{operation} #{column_name.to_s.downcase}")
324
325
 
325
326
  select_values = [
326
327
  operation_over_aggregate_column(
@@ -365,25 +366,21 @@ module ActiveRecord
365
366
  end]
366
367
  end
367
368
 
368
- # Converts the given keys to the value that the database adapter returns as
369
+ # Converts the given field to the value that the database adapter returns as
369
370
  # a usable column name:
370
371
  #
371
372
  # column_alias_for("users.id") # => "users_id"
372
373
  # column_alias_for("sum(id)") # => "sum_id"
373
374
  # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
374
375
  # column_alias_for("count(*)") # => "count_all"
375
- def column_alias_for(keys)
376
- if keys.respond_to? :name
377
- keys = "#{keys.relation.name}.#{keys.name}"
378
- end
379
-
380
- table_name = keys.to_s.downcase
381
- table_name.gsub!(/\*/, "all")
382
- table_name.gsub!(/\W+/, " ")
383
- table_name.strip!
384
- table_name.gsub!(/ +/, "_")
385
-
386
- @klass.connection.table_alias_for(table_name)
376
+ def column_alias_for(field)
377
+ column_alias = +field
378
+ column_alias.gsub!(/\*/, "all")
379
+ column_alias.gsub!(/\W+/, " ")
380
+ column_alias.strip!
381
+ column_alias.gsub!(/ +/, "_")
382
+
383
+ connection.table_alias_for(column_alias)
387
384
  end
388
385
 
389
386
  def type_for(field, &block)
@@ -411,16 +408,17 @@ module ActiveRecord
411
408
 
412
409
  def build_count_subquery(relation, column_name, distinct)
413
410
  if column_name == :all
411
+ column_alias = Arel.star
414
412
  relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
415
413
  else
416
414
  column_alias = Arel.sql("count_column")
417
415
  relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
418
416
  end
419
417
 
420
- subquery = relation.arel.as(Arel.sql("subquery_for_count"))
421
- select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false)
418
+ subquery_alias = Arel.sql("subquery_for_count")
419
+ select_value = operation_over_aggregate_column(column_alias, "count", false)
422
420
 
423
- Arel::SelectManager.new(subquery).project(select_value)
421
+ relation.build_subquery(subquery_alias, select_value)
424
422
  end
425
423
  end
426
424
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "mutex_m"
4
+
3
5
  module ActiveRecord
4
6
  module Delegation # :nodoc:
5
7
  module DelegateCache # :nodoc:
@@ -31,6 +33,10 @@ module ActiveRecord
31
33
  super
32
34
  end
33
35
 
36
+ def generate_relation_method(method)
37
+ generated_relation_methods.generate_method(method)
38
+ end
39
+
34
40
  protected
35
41
  def include_relation_methods(delegate)
36
42
  superclass.include_relation_methods(delegate) unless base_class?
@@ -39,27 +45,35 @@ module ActiveRecord
39
45
 
40
46
  private
41
47
  def generated_relation_methods
42
- @generated_relation_methods ||= Module.new.tap do |mod|
43
- mod_name = "GeneratedRelationMethods"
44
- const_set mod_name, mod
45
- private_constant mod_name
48
+ @generated_relation_methods ||= GeneratedRelationMethods.new.tap do |mod|
49
+ const_set(:GeneratedRelationMethods, mod)
50
+ private_constant :GeneratedRelationMethods
46
51
  end
47
52
  end
53
+ end
54
+
55
+ class GeneratedRelationMethods < Module # :nodoc:
56
+ include Mutex_m
57
+
58
+ def generate_method(method)
59
+ synchronize do
60
+ return if method_defined?(method)
48
61
 
49
- def generate_relation_method(method)
50
62
  if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
51
- generated_relation_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
63
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
52
64
  def #{method}(*args, &block)
53
65
  scoping { klass.#{method}(*args, &block) }
54
66
  end
55
67
  RUBY
56
68
  else
57
- generated_relation_methods.define_method(method) do |*args, &block|
69
+ define_method(method) do |*args, &block|
58
70
  scoping { klass.public_send(method, *args, &block) }
59
71
  end
60
72
  end
61
73
  end
74
+ end
62
75
  end
76
+ private_constant :GeneratedRelationMethods
63
77
 
64
78
  extend ActiveSupport::Concern
65
79
 
@@ -78,39 +92,17 @@ module ActiveRecord
78
92
  module ClassSpecificRelation # :nodoc:
79
93
  extend ActiveSupport::Concern
80
94
 
81
- included do
82
- @delegation_mutex = Mutex.new
83
- end
84
-
85
95
  module ClassMethods # :nodoc:
86
96
  def name
87
97
  superclass.name
88
98
  end
89
-
90
- def delegate_to_scoped_klass(method)
91
- @delegation_mutex.synchronize do
92
- return if method_defined?(method)
93
-
94
- if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
95
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
96
- def #{method}(*args, &block)
97
- scoping { @klass.#{method}(*args, &block) }
98
- end
99
- RUBY
100
- else
101
- define_method method do |*args, &block|
102
- scoping { @klass.public_send(method, *args, &block) }
103
- end
104
- end
105
- end
106
- end
107
99
  end
108
100
 
109
101
  private
110
102
 
111
103
  def method_missing(method, *args, &block)
112
104
  if @klass.respond_to?(method)
113
- self.class.delegate_to_scoped_klass(method)
105
+ @klass.generate_relation_method(method)
114
106
  scoping { @klass.public_send(method, *args, &block) }
115
107
  else
116
108
  super
@@ -7,8 +7,8 @@ module ActiveRecord
7
7
  ONE_AS_ONE = "1 AS one"
8
8
 
9
9
  # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
10
- # If one or more records can not be found for the requested ids, then RecordNotFound will be raised. If the primary key
11
- # is an integer, find by id coerces its arguments using +to_i+.
10
+ # If one or more records cannot be found for the requested ids, then ActiveRecord::RecordNotFound will be raised.
11
+ # If the primary key is an integer, find by id coerces its arguments by using +to_i+.
12
12
  #
13
13
  # Person.find(1) # returns the object for ID = 1
14
14
  # Person.find("1") # returns the object for ID = 1
@@ -314,7 +314,7 @@ module ActiveRecord
314
314
 
315
315
  relation = construct_relation_for_exists(conditions)
316
316
 
317
- skip_query_cache_if_necessary { connection.select_one(relation.arel, "#{name} Exists") } ? true : false
317
+ skip_query_cache_if_necessary { connection.select_one(relation.arel, "#{name} Exists?") } ? true : false
318
318
  end
319
319
 
320
320
  # This method is called whenever no records are found with either a single
@@ -355,7 +355,7 @@ module ActiveRecord
355
355
  conditions = sanitize_forbidden_attributes(conditions)
356
356
 
357
357
  if distinct_value && offset_value
358
- relation = limit(1)
358
+ relation = except(:order).limit!(1)
359
359
  else
360
360
  relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
361
361
  end
@@ -370,17 +370,22 @@ module ActiveRecord
370
370
  relation
371
371
  end
372
372
 
373
- def construct_join_dependency(associations)
374
- ActiveRecord::Associations::JoinDependency.new(
375
- klass, table, associations
376
- )
377
- end
378
-
379
373
  def apply_join_dependency(eager_loading: group_values.empty?)
380
- join_dependency = construct_join_dependency(eager_load_values + includes_values)
374
+ join_dependency = construct_join_dependency(
375
+ eager_load_values + includes_values, Arel::Nodes::OuterJoin
376
+ )
381
377
  relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
382
378
 
383
- if eager_loading && !using_limitable_reflections?(join_dependency.reflections)
379
+ if eager_loading && !(
380
+ using_limitable_reflections?(join_dependency.reflections) &&
381
+ using_limitable_reflections?(
382
+ construct_join_dependency(
383
+ select_association_list(joins_values).concat(
384
+ select_association_list(left_outer_joins_values)
385
+ ), nil
386
+ ).reflections
387
+ )
388
+ )
384
389
  if has_limit_or_offset?
385
390
  limited_ids = limited_ids_for(relation)
386
391
  limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
@@ -117,16 +117,16 @@ module ActiveRecord
117
117
  if other.klass == relation.klass
118
118
  relation.joins!(*other.joins_values)
119
119
  else
120
- joins_dependency = other.joins_values.map do |join|
120
+ associations, others = other.joins_values.partition do |join|
121
121
  case join
122
- when Hash, Symbol, Array
123
- other.send(:construct_join_dependency, join)
124
- else
125
- join
122
+ when Hash, Symbol, Array; true
126
123
  end
127
124
  end
128
125
 
129
- relation.joins!(*joins_dependency)
126
+ join_dependency = other.construct_join_dependency(
127
+ associations, Arel::Nodes::InnerJoin
128
+ )
129
+ relation.joins!(join_dependency, *others)
130
130
  end
131
131
  end
132
132
 
@@ -136,16 +136,11 @@ module ActiveRecord
136
136
  if other.klass == relation.klass
137
137
  relation.left_outer_joins!(*other.left_outer_joins_values)
138
138
  else
139
- joins_dependency = other.left_outer_joins_values.map do |join|
140
- case join
141
- when Hash, Symbol, Array
142
- other.send(:construct_join_dependency, join)
143
- else
144
- join
145
- end
146
- end
147
-
148
- relation.left_outer_joins!(*joins_dependency)
139
+ associations = other.left_outer_joins_values
140
+ join_dependency = other.construct_join_dependency(
141
+ associations, Arel::Nodes::OuterJoin
142
+ )
143
+ relation.joins!(join_dependency)
149
144
  end
150
145
  end
151
146
 
@@ -41,18 +41,42 @@ module ActiveRecord
41
41
  #
42
42
  # User.where.not(name: %w(Ko1 Nobu))
43
43
  # # SELECT * FROM users WHERE name NOT IN ('Ko1', 'Nobu')
44
- #
45
- # User.where.not(name: "Jon", role: "admin")
46
- # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin'
47
44
  def not(opts, *rest)
48
45
  opts = sanitize_forbidden_attributes(opts)
49
46
 
50
47
  where_clause = @scope.send(:where_clause_factory).build(opts, rest)
51
48
 
52
49
  @scope.references!(PredicateBuilder.references(opts)) if Hash === opts
53
- @scope.where_clause += where_clause.invert
50
+
51
+ if not_behaves_as_nor?(opts)
52
+ ActiveSupport::Deprecation.warn(<<~MSG.squish)
53
+ NOT conditions will no longer behave as NOR in Rails 6.1.
54
+ To continue using NOR conditions, NOT each conditions manually
55
+ (`#{
56
+ opts.flat_map { |key, value|
57
+ if value.is_a?(Hash) && value.size > 1
58
+ value.map { |k, v| ".where.not(#{key.inspect} => { #{k.inspect} => ... })" }
59
+ else
60
+ ".where.not(#{key.inspect} => ...)"
61
+ end
62
+ }.join
63
+ }`).
64
+ MSG
65
+ @scope.where_clause += where_clause.invert(:nor)
66
+ else
67
+ @scope.where_clause += where_clause.invert
68
+ end
69
+
54
70
  @scope
55
71
  end
72
+
73
+ private
74
+ def not_behaves_as_nor?(opts)
75
+ return false unless opts.is_a?(Hash)
76
+
77
+ opts.any? { |k, v| v.is_a?(Hash) && v.size > 1 } ||
78
+ opts.size > 1
79
+ end
56
80
  end
57
81
 
58
82
  FROZEN_EMPTY_ARRAY = [].freeze
@@ -67,11 +91,13 @@ module ActiveRecord
67
91
  end
68
92
  class_eval <<-CODE, __FILE__, __LINE__ + 1
69
93
  def #{method_name} # def includes_values
70
- get_value(#{name.inspect}) # get_value(:includes)
94
+ default = DEFAULT_VALUES[:#{name}] # default = DEFAULT_VALUES[:includes]
95
+ @values.fetch(:#{name}, default) # @values.fetch(:includes, default)
71
96
  end # end
72
97
 
73
98
  def #{method_name}=(value) # def includes_values=(value)
74
- set_value(#{name.inspect}, value) # set_value(:includes, value)
99
+ assert_mutability! # assert_mutability!
100
+ @values[:#{name}] = value # @values[:includes] = value
75
101
  end # end
76
102
  CODE
77
103
  end
@@ -100,7 +126,7 @@ module ActiveRecord
100
126
  #
101
127
  # === conditions
102
128
  #
103
- # If you want to add conditions to your included models you'll have
129
+ # If you want to add string conditions to your included models, you'll have
104
130
  # to explicitly reference them. For example:
105
131
  #
106
132
  # User.includes(:posts).where('posts.name = ?', 'example')
@@ -111,6 +137,12 @@ module ActiveRecord
111
137
  #
112
138
  # Note that #includes works with association names while #references needs
113
139
  # the actual table name.
140
+ #
141
+ # If you pass the conditions via hash, you don't need to call #references
142
+ # explicitly, as #where references the tables for you. For example, this
143
+ # will work correctly:
144
+ #
145
+ # User.includes(:posts).where(posts: { name: 'example' })
114
146
  def includes(*args)
115
147
  check_if_method_has_arguments!(:includes, args)
116
148
  spawn.includes!(*args)
@@ -154,6 +186,19 @@ module ActiveRecord
154
186
  self
155
187
  end
156
188
 
189
+ # Extracts a named +association+ from the relation. The named association is first preloaded,
190
+ # then the individual association records are collected from the relation. Like so:
191
+ #
192
+ # account.memberships.extract_associated(:user)
193
+ # # => Returns collection of User records
194
+ #
195
+ # This is short-hand for:
196
+ #
197
+ # account.memberships.preload(:user).collect(&:user)
198
+ def extract_associated(association)
199
+ preload(association).collect(&association)
200
+ end
201
+
157
202
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
158
203
  # and should therefore be JOINed in any query rather than loaded separately.
159
204
  # This method only works in conjunction with #includes.
@@ -233,13 +278,31 @@ module ActiveRecord
233
278
  def _select!(*fields) # :nodoc:
234
279
  fields.reject!(&:blank?)
235
280
  fields.flatten!
236
- fields.map! do |field|
237
- klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
238
- end
239
281
  self.select_values += fields
240
282
  self
241
283
  end
242
284
 
285
+ # Allows you to change a previously set select statement.
286
+ #
287
+ # Post.select(:title, :body)
288
+ # # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
289
+ #
290
+ # Post.select(:title, :body).reselect(:created_at)
291
+ # # SELECT `posts`.`created_at` FROM `posts`
292
+ #
293
+ # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
294
+ # Note that we're unscoping the entire select statement.
295
+ def reselect(*args)
296
+ check_if_method_has_arguments!(:reselect, args)
297
+ spawn.reselect!(*args)
298
+ end
299
+
300
+ # Same as #reselect but operates on relation in-place instead of copying.
301
+ def reselect!(*args) # :nodoc:
302
+ self.select_values = args
303
+ self
304
+ end
305
+
243
306
  # Allows to specify a group attribute:
244
307
  #
245
308
  # User.group(:name)
@@ -320,7 +383,7 @@ module ActiveRecord
320
383
 
321
384
  # Same as #reorder but operates on relation in-place instead of copying.
322
385
  def reorder!(*args) # :nodoc:
323
- preprocess_order_args(args)
386
+ preprocess_order_args(args) unless args.all?(&:blank?)
324
387
 
325
388
  self.reordering_value = true
326
389
  self.order_values = args
@@ -328,8 +391,8 @@ module ActiveRecord
328
391
  end
329
392
 
330
393
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
331
- :limit, :offset, :joins, :left_outer_joins,
332
- :includes, :from, :readonly, :having])
394
+ :limit, :offset, :joins, :left_outer_joins, :annotate,
395
+ :includes, :from, :readonly, :having, :optimizer_hints])
333
396
 
334
397
  # Removes an unwanted relation that is already defined on a chain of relations.
335
398
  # This is useful when passing around chains of relations and would like to
@@ -380,7 +443,8 @@ module ActiveRecord
380
443
  if !VALID_UNSCOPING_VALUES.include?(scope)
381
444
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
382
445
  end
383
- set_value(scope, DEFAULT_VALUES[scope])
446
+ assert_mutability!
447
+ @values[scope] = DEFAULT_VALUES[scope]
384
448
  when Hash
385
449
  scope.each do |key, target_value|
386
450
  if key != :where
@@ -880,6 +944,29 @@ module ActiveRecord
880
944
  self
881
945
  end
882
946
 
947
+ # Specify optimizer hints to be used in the SELECT statement.
948
+ #
949
+ # Example (for MySQL):
950
+ #
951
+ # Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
952
+ # # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
953
+ #
954
+ # Example (for PostgreSQL with pg_hint_plan):
955
+ #
956
+ # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
957
+ # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
958
+ def optimizer_hints(*args)
959
+ check_if_method_has_arguments!(:optimizer_hints, args)
960
+ spawn.optimizer_hints!(*args)
961
+ end
962
+
963
+ def optimizer_hints!(*args) # :nodoc:
964
+ args.flatten!
965
+
966
+ self.optimizer_hints_values |= args
967
+ self
968
+ end
969
+
883
970
  # Reverse the existing order clause on the relation.
884
971
  #
885
972
  # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
@@ -904,23 +991,47 @@ module ActiveRecord
904
991
  self
905
992
  end
906
993
 
994
+ # Adds an SQL comment to queries generated from this relation. For example:
995
+ #
996
+ # User.annotate("selecting user names").select(:name)
997
+ # # SELECT "users"."name" FROM "users" /* selecting user names */
998
+ #
999
+ # User.annotate("selecting", "user", "names").select(:name)
1000
+ # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
1001
+ #
1002
+ # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
1003
+ def annotate(*args)
1004
+ check_if_method_has_arguments!(:annotate, args)
1005
+ spawn.annotate!(*args)
1006
+ end
1007
+
1008
+ # Like #annotate, but modifies relation in place.
1009
+ def annotate!(*args) # :nodoc:
1010
+ self.annotate_values += args
1011
+ self
1012
+ end
1013
+
907
1014
  # Returns the Arel object associated with the relation.
908
1015
  def arel(aliases = nil) # :nodoc:
909
1016
  @arel ||= build_arel(aliases)
910
1017
  end
911
1018
 
912
- private
913
- # Returns a relation value with a given name
914
- def get_value(name)
915
- @values.fetch(name, DEFAULT_VALUES[name])
916
- end
1019
+ def construct_join_dependency(associations, join_type) # :nodoc:
1020
+ ActiveRecord::Associations::JoinDependency.new(
1021
+ klass, table, associations, join_type
1022
+ )
1023
+ end
1024
+
1025
+ protected
1026
+ def build_subquery(subquery_alias, select_value) # :nodoc:
1027
+ subquery = except(:optimizer_hints).arel.as(subquery_alias)
917
1028
 
918
- # Sets the relation value with the given name
919
- def set_value(name, value)
920
- assert_mutability!
921
- @values[name] = value
1029
+ Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
1030
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1031
+ end
922
1032
  end
923
1033
 
1034
+ private
924
1035
  def assert_mutability!
925
1036
  raise ImmutableRelation if @loaded
926
1037
  raise ImmutableRelation if defined?(@arel) && @arel
@@ -929,8 +1040,11 @@ module ActiveRecord
929
1040
  def build_arel(aliases)
930
1041
  arel = Arel::SelectManager.new(table)
931
1042
 
932
- aliases = build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty?
933
- build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty?
1043
+ if !joins_values.empty?
1044
+ build_joins(arel, joins_values.flatten, aliases)
1045
+ elsif !left_outer_joins_values.empty?
1046
+ build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases)
1047
+ end
934
1048
 
935
1049
  arel.where(where_clause.ast) unless where_clause.empty?
936
1050
  arel.having(having_clause.ast) unless having_clause.empty?
@@ -956,9 +1070,11 @@ module ActiveRecord
956
1070
 
957
1071
  build_select(arel)
958
1072
 
1073
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
959
1074
  arel.distinct(distinct_value)
960
1075
  arel.from(build_from) unless from_clause.empty?
961
1076
  arel.lock(lock_value) if lock_value
1077
+ arel.comment(*annotate_values) unless annotate_values.empty?
962
1078
 
963
1079
  arel
964
1080
  end
@@ -978,32 +1094,68 @@ module ActiveRecord
978
1094
  end
979
1095
  end
980
1096
 
981
- def build_left_outer_joins(manager, outer_joins, aliases)
982
- buckets = outer_joins.group_by do |join|
983
- case join
1097
+ def select_association_list(associations)
1098
+ result = []
1099
+ associations.each do |association|
1100
+ case association
984
1101
  when Hash, Symbol, Array
985
- :association_join
986
- when ActiveRecord::Associations::JoinDependency
987
- :stashed_join
1102
+ result << association
988
1103
  else
989
- raise ArgumentError, "only Hash, Symbol and Array are allowed"
1104
+ yield if block_given?
990
1105
  end
991
1106
  end
1107
+ result
1108
+ end
1109
+
1110
+ def valid_association_list(associations)
1111
+ select_association_list(associations) do
1112
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1113
+ end
1114
+ end
992
1115
 
1116
+ def build_left_outer_joins(manager, outer_joins, aliases)
1117
+ buckets = Hash.new { |h, k| h[k] = [] }
1118
+ buckets[:association_join] = valid_association_list(outer_joins)
993
1119
  build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
994
1120
  end
995
1121
 
996
1122
  def build_joins(manager, joins, aliases)
997
- buckets = joins.group_by do |join|
1123
+ buckets = Hash.new { |h, k| h[k] = [] }
1124
+
1125
+ unless left_outer_joins_values.empty?
1126
+ left_joins = valid_association_list(left_outer_joins_values.flatten)
1127
+ buckets[:stashed_join] << construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1128
+ end
1129
+
1130
+ if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1131
+ buckets[:stashed_join] << joins.pop if joins.last.base_klass == klass
1132
+ end
1133
+
1134
+ joins.map! do |join|
1135
+ if join.is_a?(String)
1136
+ table.create_string_join(Arel.sql(join.strip)) unless join.blank?
1137
+ else
1138
+ join
1139
+ end
1140
+ end.delete_if(&:blank?).uniq!
1141
+
1142
+ while joins.first.is_a?(Arel::Nodes::Join)
1143
+ join_node = joins.shift
1144
+ if join_node.is_a?(Arel::Nodes::StringJoin) && !buckets[:stashed_join].empty?
1145
+ buckets[:join_node] << join_node
1146
+ else
1147
+ buckets[:leading_join] << join_node
1148
+ end
1149
+ end
1150
+
1151
+ joins.each do |join|
998
1152
  case join
999
- when String
1000
- :string_join
1001
1153
  when Hash, Symbol, Array
1002
- :association_join
1154
+ buckets[:association_join] << join
1003
1155
  when ActiveRecord::Associations::JoinDependency
1004
- :stashed_join
1156
+ buckets[:stashed_join] << join
1005
1157
  when Arel::Nodes::Join
1006
- :join_node
1158
+ buckets[:join_node] << join
1007
1159
  else
1008
1160
  raise "unknown class: %s" % join.class.name
1009
1161
  end
@@ -1013,31 +1165,21 @@ module ActiveRecord
1013
1165
  end
1014
1166
 
1015
1167
  def build_join_query(manager, buckets, join_type, aliases)
1016
- buckets.default = []
1017
-
1018
1168
  association_joins = buckets[:association_join]
1019
1169
  stashed_joins = buckets[:stashed_join]
1020
- join_nodes = buckets[:join_node].uniq
1021
- string_joins = buckets[:string_join].map(&:strip).uniq
1022
-
1023
- join_list = join_nodes + convert_join_strings_to_ast(string_joins)
1024
- alias_tracker = alias_tracker(join_list, aliases)
1025
-
1026
- join_dependency = construct_join_dependency(association_joins)
1027
-
1028
- joins = join_dependency.join_constraints(stashed_joins, join_type, alias_tracker)
1029
- joins.each { |join| manager.from(join) }
1170
+ leading_joins = buckets[:leading_join]
1171
+ join_nodes = buckets[:join_node]
1030
1172
 
1031
- manager.join_sources.concat(join_list)
1173
+ join_sources = manager.join_sources
1174
+ join_sources.concat(leading_joins) unless leading_joins.empty?
1032
1175
 
1033
- alias_tracker.aliases
1034
- end
1176
+ unless association_joins.empty? && stashed_joins.empty?
1177
+ alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1178
+ join_dependency = construct_join_dependency(association_joins, join_type)
1179
+ join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
1180
+ end
1035
1181
 
1036
- def convert_join_strings_to_ast(joins)
1037
- joins
1038
- .flatten
1039
- .reject(&:blank?)
1040
- .map { |join| table.create_string_join(Arel.sql(join)) }
1182
+ join_sources.concat(join_nodes) unless join_nodes.empty?
1041
1183
  end
1042
1184
 
1043
1185
  def build_select(arel)
@@ -1054,10 +1196,11 @@ module ActiveRecord
1054
1196
  columns.flat_map do |field|
1055
1197
  case field
1056
1198
  when Symbol
1057
- field = field.to_s
1058
- arel_column(field) { connection.quote_table_name(field) }
1199
+ arel_column(field.to_s) do |attr_name|
1200
+ connection.quote_table_name(attr_name)
1201
+ end
1059
1202
  when String
1060
- arel_column(field) { field }
1203
+ arel_column(field, &:itself)
1061
1204
  when Proc
1062
1205
  field.call
1063
1206
  else
@@ -1067,17 +1210,20 @@ module ActiveRecord
1067
1210
  end
1068
1211
 
1069
1212
  def arel_column(field)
1070
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1213
+ field = klass.attribute_aliases[field] || field
1071
1214
  from = from_clause.name || from_clause.value
1072
1215
 
1073
- if klass.columns_hash.key?(field) &&
1074
- (!from || from == table.name || from == connection.quote_table_name(table.name))
1216
+ if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1075
1217
  arel_attribute(field)
1076
1218
  else
1077
- yield
1219
+ yield field
1078
1220
  end
1079
1221
  end
1080
1222
 
1223
+ def table_name_matches?(from)
1224
+ /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
1225
+ end
1226
+
1081
1227
  def reverse_sql_order(order_query)
1082
1228
  if order_query.empty?
1083
1229
  return [arel_attribute(primary_key).desc] if primary_key
@@ -1093,7 +1239,7 @@ module ActiveRecord
1093
1239
  o.reverse
1094
1240
  when String
1095
1241
  if does_not_support_reverse?(o)
1096
- raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
1242
+ raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1097
1243
  end
1098
1244
  o.split(",").map! do |s|
1099
1245
  s.strip!
@@ -1139,6 +1285,7 @@ module ActiveRecord
1139
1285
  end
1140
1286
 
1141
1287
  def preprocess_order_args(order_args)
1288
+ order_args.reject!(&:blank?)
1142
1289
  order_args.map! do |arg|
1143
1290
  klass.sanitize_sql_for_order(arg)
1144
1291
  end
@@ -1146,7 +1293,7 @@ module ActiveRecord
1146
1293
 
1147
1294
  @klass.disallow_raw_sql!(
1148
1295
  order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1149
- permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
1296
+ permit: connection.column_name_with_order_matcher
1150
1297
  )
1151
1298
 
1152
1299
  validate_order_args(order_args)
@@ -1159,20 +1306,14 @@ module ActiveRecord
1159
1306
  order_args.map! do |arg|
1160
1307
  case arg
1161
1308
  when Symbol
1162
- arg = arg.to_s
1163
- arel_column(arg) {
1164
- Arel.sql(connection.quote_table_name(arg))
1165
- }.asc
1309
+ order_column(arg.to_s).asc
1166
1310
  when Hash
1167
1311
  arg.map { |field, dir|
1168
1312
  case field
1169
1313
  when Arel::Nodes::SqlLiteral
1170
1314
  field.send(dir.downcase)
1171
1315
  else
1172
- field = field.to_s
1173
- arel_column(field) {
1174
- Arel.sql(connection.quote_table_name(field))
1175
- }.send(dir.downcase)
1316
+ order_column(field.to_s).send(dir.downcase)
1176
1317
  end
1177
1318
  }
1178
1319
  else
@@ -1181,6 +1322,16 @@ module ActiveRecord
1181
1322
  end.flatten!
1182
1323
  end
1183
1324
 
1325
+ def order_column(field)
1326
+ arel_column(field) do |attr_name|
1327
+ if attr_name == "count" && !group_values.empty?
1328
+ arel_attribute(attr_name)
1329
+ else
1330
+ Arel.sql(connection.quote_table_name(attr_name))
1331
+ end
1332
+ end
1333
+ end
1334
+
1184
1335
  # Checks to make sure that the arguments are not blank. Note that if some
1185
1336
  # blank-like object were initially passed into the query method, then this
1186
1337
  # method will not raise an error.
@@ -1207,7 +1358,8 @@ module ActiveRecord
1207
1358
  def structurally_incompatible_values_for_or(other)
1208
1359
  values = other.values
1209
1360
  STRUCTURAL_OR_METHODS.reject do |method|
1210
- get_value(method) == values.fetch(method, DEFAULT_VALUES[method])
1361
+ default = DEFAULT_VALUES[method]
1362
+ @values.fetch(method, default) == values.fetch(method, default)
1211
1363
  end
1212
1364
  end
1213
1365