activerecord 6.0.0.beta1 → 6.0.1.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 (158) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +529 -10
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +7 -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 +27 -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 +5 -6
  16. data/lib/active_record/associations/collection_proxy.rb +13 -42
  17. data/lib/active_record/associations/has_many_association.rb +1 -9
  18. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  19. data/lib/active_record/associations/join_dependency.rb +14 -9
  20. data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
  21. data/lib/active_record/associations/preloader.rb +12 -7
  22. data/lib/active_record/associations/preloader/association.rb +37 -34
  23. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  24. data/lib/active_record/attribute_methods.rb +3 -53
  25. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  26. data/lib/active_record/attribute_methods/dirty.rb +47 -14
  27. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  28. data/lib/active_record/attribute_methods/query.rb +2 -3
  29. data/lib/active_record/attribute_methods/read.rb +3 -9
  30. data/lib/active_record/attribute_methods/write.rb +6 -12
  31. data/lib/active_record/attributes.rb +13 -0
  32. data/lib/active_record/autosave_association.rb +21 -7
  33. data/lib/active_record/base.rb +0 -1
  34. data/lib/active_record/callbacks.rb +3 -3
  35. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +134 -23
  36. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  37. data/lib/active_record/connection_adapters/abstract/database_statements.rb +105 -70
  38. data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -5
  39. data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
  40. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
  41. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
  42. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  43. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
  44. data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
  45. data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -35
  46. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +106 -138
  47. data/lib/active_record/connection_adapters/column.rb +17 -13
  48. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  49. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
  50. data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
  51. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  52. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  53. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  54. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
  55. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  56. data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
  57. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  58. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
  59. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  60. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  61. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  62. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
  63. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  64. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
  65. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
  66. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  67. data/lib/active_record/connection_adapters/postgresql_adapter.rb +95 -24
  68. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  69. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  70. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  71. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  72. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
  73. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +73 -118
  74. data/lib/active_record/connection_handling.rb +40 -17
  75. data/lib/active_record/core.rb +35 -24
  76. data/lib/active_record/database_configurations.rb +99 -50
  77. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  78. data/lib/active_record/database_configurations/url_config.rb +21 -16
  79. data/lib/active_record/dynamic_matchers.rb +1 -1
  80. data/lib/active_record/enum.rb +15 -0
  81. data/lib/active_record/errors.rb +18 -13
  82. data/lib/active_record/fixtures.rb +11 -6
  83. data/lib/active_record/gem_version.rb +2 -2
  84. data/lib/active_record/inheritance.rb +1 -1
  85. data/lib/active_record/insert_all.rb +179 -0
  86. data/lib/active_record/integration.rb +13 -1
  87. data/lib/active_record/internal_metadata.rb +5 -1
  88. data/lib/active_record/locking/optimistic.rb +3 -4
  89. data/lib/active_record/log_subscriber.rb +1 -1
  90. data/lib/active_record/middleware/database_selector.rb +75 -0
  91. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  92. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  93. data/lib/active_record/migration.rb +62 -44
  94. data/lib/active_record/migration/command_recorder.rb +28 -14
  95. data/lib/active_record/migration/compatibility.rb +72 -63
  96. data/lib/active_record/model_schema.rb +3 -0
  97. data/lib/active_record/persistence.rb +212 -19
  98. data/lib/active_record/querying.rb +18 -14
  99. data/lib/active_record/railtie.rb +9 -1
  100. data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
  101. data/lib/active_record/railties/databases.rake +124 -25
  102. data/lib/active_record/reflection.rb +18 -32
  103. data/lib/active_record/relation.rb +185 -35
  104. data/lib/active_record/relation/calculations.rb +40 -44
  105. data/lib/active_record/relation/delegation.rb +23 -31
  106. data/lib/active_record/relation/finder_methods.rb +23 -14
  107. data/lib/active_record/relation/merger.rb +11 -16
  108. data/lib/active_record/relation/query_attribute.rb +5 -3
  109. data/lib/active_record/relation/query_methods.rb +230 -69
  110. data/lib/active_record/relation/spawn_methods.rb +1 -1
  111. data/lib/active_record/relation/where_clause.rb +10 -10
  112. data/lib/active_record/sanitization.rb +33 -4
  113. data/lib/active_record/schema.rb +1 -1
  114. data/lib/active_record/schema_dumper.rb +10 -1
  115. data/lib/active_record/schema_migration.rb +1 -1
  116. data/lib/active_record/scoping.rb +6 -7
  117. data/lib/active_record/scoping/default.rb +7 -15
  118. data/lib/active_record/scoping/named.rb +10 -2
  119. data/lib/active_record/statement_cache.rb +2 -2
  120. data/lib/active_record/store.rb +48 -0
  121. data/lib/active_record/table_metadata.rb +9 -13
  122. data/lib/active_record/tasks/database_tasks.rb +109 -6
  123. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  124. data/lib/active_record/test_databases.rb +1 -16
  125. data/lib/active_record/test_fixtures.rb +2 -2
  126. data/lib/active_record/timestamp.rb +35 -19
  127. data/lib/active_record/touch_later.rb +4 -2
  128. data/lib/active_record/transactions.rb +56 -46
  129. data/lib/active_record/type_caster/connection.rb +16 -10
  130. data/lib/active_record/validations.rb +1 -0
  131. data/lib/active_record/validations/uniqueness.rb +4 -4
  132. data/lib/arel.rb +18 -4
  133. data/lib/arel/insert_manager.rb +3 -3
  134. data/lib/arel/nodes.rb +2 -1
  135. data/lib/arel/nodes/and.rb +1 -1
  136. data/lib/arel/nodes/case.rb +1 -1
  137. data/lib/arel/nodes/comment.rb +29 -0
  138. data/lib/arel/nodes/select_core.rb +16 -12
  139. data/lib/arel/nodes/unary.rb +1 -0
  140. data/lib/arel/nodes/values_list.rb +2 -17
  141. data/lib/arel/select_manager.rb +10 -10
  142. data/lib/arel/visitors/depth_first.rb +7 -2
  143. data/lib/arel/visitors/dot.rb +7 -2
  144. data/lib/arel/visitors/ibm_db.rb +13 -0
  145. data/lib/arel/visitors/informix.rb +6 -0
  146. data/lib/arel/visitors/mssql.rb +15 -1
  147. data/lib/arel/visitors/oracle12.rb +4 -5
  148. data/lib/arel/visitors/postgresql.rb +4 -10
  149. data/lib/arel/visitors/to_sql.rb +107 -131
  150. data/lib/arel/visitors/visitor.rb +9 -5
  151. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  152. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  153. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  154. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  155. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  156. metadata +19 -12
  157. data/lib/active_record/collection_cache_key.rb +0 -53
  158. data/lib/arel/nodes/values.rb +0 -16
@@ -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
@@ -132,7 +124,7 @@ module ActiveRecord
132
124
 
133
125
  private
134
126
  def respond_to_missing?(method, _)
135
- super || @klass.respond_to?(method) || arel.respond_to?(method)
127
+ super || @klass.respond_to?(method)
136
128
  end
137
129
  end
138
130
  end
@@ -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
@@ -307,8 +307,6 @@ module ActiveRecord
307
307
 
308
308
  return false if !conditions || limit_value == 0
309
309
 
310
- conditions = sanitize_forbidden_attributes(conditions)
311
-
312
310
  if eager_loading?
313
311
  relation = apply_join_dependency(eager_loading: false)
314
312
  return relation.exists?(conditions)
@@ -316,7 +314,7 @@ module ActiveRecord
316
314
 
317
315
  relation = construct_relation_for_exists(conditions)
318
316
 
319
- skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false
317
+ skip_query_cache_if_necessary { connection.select_one(relation.arel, "#{name} Exists?") } ? true : false
320
318
  end
321
319
 
322
320
  # This method is called whenever no records are found with either a single
@@ -354,7 +352,13 @@ module ActiveRecord
354
352
  end
355
353
 
356
354
  def construct_relation_for_exists(conditions)
357
- relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
355
+ conditions = sanitize_forbidden_attributes(conditions)
356
+
357
+ if distinct_value && offset_value
358
+ relation = except(:order).limit!(1)
359
+ else
360
+ relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1)
361
+ end
358
362
 
359
363
  case conditions
360
364
  when Array, Hash
@@ -366,17 +370,22 @@ module ActiveRecord
366
370
  relation
367
371
  end
368
372
 
369
- def construct_join_dependency(associations)
370
- ActiveRecord::Associations::JoinDependency.new(
371
- klass, table, associations
372
- )
373
- end
374
-
375
373
  def apply_join_dependency(eager_loading: group_values.empty?)
376
- 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
+ )
377
377
  relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
378
378
 
379
- 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
+ )
380
389
  if has_limit_or_offset?
381
390
  limited_ids = limited_ids_for(relation)
382
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
 
@@ -18,8 +18,10 @@ module ActiveRecord
18
18
  end
19
19
 
20
20
  def nil?
21
- !value_before_type_cast.is_a?(StatementCache::Substitute) &&
22
- (value_before_type_cast.nil? || value_for_database.nil?)
21
+ unless value_before_type_cast.is_a?(StatementCache::Substitute)
22
+ value_before_type_cast.nil? ||
23
+ type.respond_to?(:subtype, true) && value_for_database.nil?
24
+ end
23
25
  rescue ::RangeError
24
26
  end
25
27
 
@@ -32,7 +34,7 @@ module ActiveRecord
32
34
  if defined?(@_unboundable)
33
35
  @_unboundable
34
36
  else
35
- value_for_database
37
+ value_for_database unless value_before_type_cast.is_a?(StatementCache::Substitute)
36
38
  @_unboundable = nil
37
39
  end
38
40
  rescue ::RangeError
@@ -41,18 +41,31 @@ 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
+ (`#{ opts.keys.map { |key| ".where.not(#{key.inspect} => ...)" }.join }`).
56
+ MSG
57
+ @scope.where_clause += where_clause.invert(:nor)
58
+ else
59
+ @scope.where_clause += where_clause.invert
60
+ end
61
+
54
62
  @scope
55
63
  end
64
+
65
+ private
66
+ def not_behaves_as_nor?(opts)
67
+ opts.is_a?(Hash) && opts.size > 1
68
+ end
56
69
  end
57
70
 
58
71
  FROZEN_EMPTY_ARRAY = [].freeze
@@ -67,11 +80,13 @@ module ActiveRecord
67
80
  end
68
81
  class_eval <<-CODE, __FILE__, __LINE__ + 1
69
82
  def #{method_name} # def includes_values
70
- get_value(#{name.inspect}) # get_value(:includes)
83
+ default = DEFAULT_VALUES[:#{name}] # default = DEFAULT_VALUES[:includes]
84
+ @values.fetch(:#{name}, default) # @values.fetch(:includes, default)
71
85
  end # end
72
86
 
73
87
  def #{method_name}=(value) # def includes_values=(value)
74
- set_value(#{name.inspect}, value) # set_value(:includes, value)
88
+ assert_mutability! # assert_mutability!
89
+ @values[:#{name}] = value # @values[:includes] = value
75
90
  end # end
76
91
  CODE
77
92
  end
@@ -100,7 +115,7 @@ module ActiveRecord
100
115
  #
101
116
  # === conditions
102
117
  #
103
- # If you want to add conditions to your included models you'll have
118
+ # If you want to add string conditions to your included models, you'll have
104
119
  # to explicitly reference them. For example:
105
120
  #
106
121
  # User.includes(:posts).where('posts.name = ?', 'example')
@@ -111,6 +126,12 @@ module ActiveRecord
111
126
  #
112
127
  # Note that #includes works with association names while #references needs
113
128
  # the actual table name.
129
+ #
130
+ # If you pass the conditions via hash, you don't need to call #references
131
+ # explicitly, as #where references the tables for you. For example, this
132
+ # will work correctly:
133
+ #
134
+ # User.includes(:posts).where(posts: { name: 'example' })
114
135
  def includes(*args)
115
136
  check_if_method_has_arguments!(:includes, args)
116
137
  spawn.includes!(*args)
@@ -154,6 +175,19 @@ module ActiveRecord
154
175
  self
155
176
  end
156
177
 
178
+ # Extracts a named +association+ from the relation. The named association is first preloaded,
179
+ # then the individual association records are collected from the relation. Like so:
180
+ #
181
+ # account.memberships.extract_associated(:user)
182
+ # # => Returns collection of User records
183
+ #
184
+ # This is short-hand for:
185
+ #
186
+ # account.memberships.preload(:user).collect(&:user)
187
+ def extract_associated(association)
188
+ preload(association).collect(&association)
189
+ end
190
+
157
191
  # Use to indicate that the given +table_names+ are referenced by an SQL string,
158
192
  # and should therefore be JOINed in any query rather than loaded separately.
159
193
  # This method only works in conjunction with #includes.
@@ -233,13 +267,31 @@ module ActiveRecord
233
267
  def _select!(*fields) # :nodoc:
234
268
  fields.reject!(&:blank?)
235
269
  fields.flatten!
236
- fields.map! do |field|
237
- klass.attribute_alias?(field) ? klass.attribute_alias(field).to_sym : field
238
- end
239
270
  self.select_values += fields
240
271
  self
241
272
  end
242
273
 
274
+ # Allows you to change a previously set select statement.
275
+ #
276
+ # Post.select(:title, :body)
277
+ # # SELECT `posts`.`title`, `posts`.`body` FROM `posts`
278
+ #
279
+ # Post.select(:title, :body).reselect(:created_at)
280
+ # # SELECT `posts`.`created_at` FROM `posts`
281
+ #
282
+ # This is short-hand for <tt>unscope(:select).select(fields)</tt>.
283
+ # Note that we're unscoping the entire select statement.
284
+ def reselect(*args)
285
+ check_if_method_has_arguments!(:reselect, args)
286
+ spawn.reselect!(*args)
287
+ end
288
+
289
+ # Same as #reselect but operates on relation in-place instead of copying.
290
+ def reselect!(*args) # :nodoc:
291
+ self.select_values = args
292
+ self
293
+ end
294
+
243
295
  # Allows to specify a group attribute:
244
296
  #
245
297
  # User.group(:name)
@@ -320,7 +372,7 @@ module ActiveRecord
320
372
 
321
373
  # Same as #reorder but operates on relation in-place instead of copying.
322
374
  def reorder!(*args) # :nodoc:
323
- preprocess_order_args(args)
375
+ preprocess_order_args(args) unless args.all?(&:blank?)
324
376
 
325
377
  self.reordering_value = true
326
378
  self.order_values = args
@@ -328,8 +380,8 @@ module ActiveRecord
328
380
  end
329
381
 
330
382
  VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock,
331
- :limit, :offset, :joins, :left_outer_joins,
332
- :includes, :from, :readonly, :having])
383
+ :limit, :offset, :joins, :left_outer_joins, :annotate,
384
+ :includes, :from, :readonly, :having, :optimizer_hints])
333
385
 
334
386
  # Removes an unwanted relation that is already defined on a chain of relations.
335
387
  # This is useful when passing around chains of relations and would like to
@@ -380,7 +432,8 @@ module ActiveRecord
380
432
  if !VALID_UNSCOPING_VALUES.include?(scope)
381
433
  raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
382
434
  end
383
- set_value(scope, DEFAULT_VALUES[scope])
435
+ assert_mutability!
436
+ @values[scope] = DEFAULT_VALUES[scope]
384
437
  when Hash
385
438
  scope.each do |key, target_value|
386
439
  if key != :where
@@ -880,6 +933,29 @@ module ActiveRecord
880
933
  self
881
934
  end
882
935
 
936
+ # Specify optimizer hints to be used in the SELECT statement.
937
+ #
938
+ # Example (for MySQL):
939
+ #
940
+ # Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
941
+ # # SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`
942
+ #
943
+ # Example (for PostgreSQL with pg_hint_plan):
944
+ #
945
+ # Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
946
+ # # SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"
947
+ def optimizer_hints(*args)
948
+ check_if_method_has_arguments!(:optimizer_hints, args)
949
+ spawn.optimizer_hints!(*args)
950
+ end
951
+
952
+ def optimizer_hints!(*args) # :nodoc:
953
+ args.flatten!
954
+
955
+ self.optimizer_hints_values |= args
956
+ self
957
+ end
958
+
883
959
  # Reverse the existing order clause on the relation.
884
960
  #
885
961
  # User.order('name ASC').reverse_order # generated SQL has 'ORDER BY name DESC'
@@ -904,23 +980,47 @@ module ActiveRecord
904
980
  self
905
981
  end
906
982
 
983
+ # Adds an SQL comment to queries generated from this relation. For example:
984
+ #
985
+ # User.annotate("selecting user names").select(:name)
986
+ # # SELECT "users"."name" FROM "users" /* selecting user names */
987
+ #
988
+ # User.annotate("selecting", "user", "names").select(:name)
989
+ # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */
990
+ #
991
+ # The SQL block comment delimiters, "/*" and "*/", will be added automatically.
992
+ def annotate(*args)
993
+ check_if_method_has_arguments!(:annotate, args)
994
+ spawn.annotate!(*args)
995
+ end
996
+
997
+ # Like #annotate, but modifies relation in place.
998
+ def annotate!(*args) # :nodoc:
999
+ self.annotate_values += args
1000
+ self
1001
+ end
1002
+
907
1003
  # Returns the Arel object associated with the relation.
908
1004
  def arel(aliases = nil) # :nodoc:
909
1005
  @arel ||= build_arel(aliases)
910
1006
  end
911
1007
 
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
1008
+ def construct_join_dependency(associations, join_type) # :nodoc:
1009
+ ActiveRecord::Associations::JoinDependency.new(
1010
+ klass, table, associations, join_type
1011
+ )
1012
+ end
917
1013
 
918
- # Sets the relation value with the given name
919
- def set_value(name, value)
920
- assert_mutability!
921
- @values[name] = value
1014
+ protected
1015
+ def build_subquery(subquery_alias, select_value) # :nodoc:
1016
+ subquery = except(:optimizer_hints).arel.as(subquery_alias)
1017
+
1018
+ Arel::SelectManager.new(subquery).project(select_value).tap do |arel|
1019
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
1020
+ end
922
1021
  end
923
1022
 
1023
+ private
924
1024
  def assert_mutability!
925
1025
  raise ImmutableRelation if @loaded
926
1026
  raise ImmutableRelation if defined?(@arel) && @arel
@@ -929,8 +1029,11 @@ module ActiveRecord
929
1029
  def build_arel(aliases)
930
1030
  arel = Arel::SelectManager.new(table)
931
1031
 
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?
1032
+ if !joins_values.empty?
1033
+ build_joins(arel, joins_values.flatten, aliases)
1034
+ elsif !left_outer_joins_values.empty?
1035
+ build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases)
1036
+ end
934
1037
 
935
1038
  arel.where(where_clause.ast) unless where_clause.empty?
936
1039
  arel.having(having_clause.ast) unless having_clause.empty?
@@ -956,9 +1059,11 @@ module ActiveRecord
956
1059
 
957
1060
  build_select(arel)
958
1061
 
1062
+ arel.optimizer_hints(*optimizer_hints_values) unless optimizer_hints_values.empty?
959
1063
  arel.distinct(distinct_value)
960
1064
  arel.from(build_from) unless from_clause.empty?
961
1065
  arel.lock(lock_value) if lock_value
1066
+ arel.comment(*annotate_values) unless annotate_values.empty?
962
1067
 
963
1068
  arel
964
1069
  end
@@ -978,32 +1083,68 @@ module ActiveRecord
978
1083
  end
979
1084
  end
980
1085
 
981
- def build_left_outer_joins(manager, outer_joins, aliases)
982
- buckets = outer_joins.group_by do |join|
983
- case join
1086
+ def select_association_list(associations)
1087
+ result = []
1088
+ associations.each do |association|
1089
+ case association
984
1090
  when Hash, Symbol, Array
985
- :association_join
986
- when ActiveRecord::Associations::JoinDependency
987
- :stashed_join
1091
+ result << association
988
1092
  else
989
- raise ArgumentError, "only Hash, Symbol and Array are allowed"
1093
+ yield if block_given?
990
1094
  end
991
1095
  end
1096
+ result
1097
+ end
1098
+
1099
+ def valid_association_list(associations)
1100
+ select_association_list(associations) do
1101
+ raise ArgumentError, "only Hash, Symbol and Array are allowed"
1102
+ end
1103
+ end
992
1104
 
1105
+ def build_left_outer_joins(manager, outer_joins, aliases)
1106
+ buckets = Hash.new { |h, k| h[k] = [] }
1107
+ buckets[:association_join] = valid_association_list(outer_joins)
993
1108
  build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
994
1109
  end
995
1110
 
996
1111
  def build_joins(manager, joins, aliases)
997
- buckets = joins.group_by do |join|
1112
+ buckets = Hash.new { |h, k| h[k] = [] }
1113
+
1114
+ unless left_outer_joins_values.empty?
1115
+ left_joins = valid_association_list(left_outer_joins_values.flatten)
1116
+ buckets[:stashed_join] << construct_join_dependency(left_joins, Arel::Nodes::OuterJoin)
1117
+ end
1118
+
1119
+ if joins.last.is_a?(ActiveRecord::Associations::JoinDependency)
1120
+ buckets[:stashed_join] << joins.pop if joins.last.base_klass == klass
1121
+ end
1122
+
1123
+ joins.map! do |join|
1124
+ if join.is_a?(String)
1125
+ table.create_string_join(Arel.sql(join.strip)) unless join.blank?
1126
+ else
1127
+ join
1128
+ end
1129
+ end.delete_if(&:blank?).uniq!
1130
+
1131
+ while joins.first.is_a?(Arel::Nodes::Join)
1132
+ join_node = joins.shift
1133
+ if join_node.is_a?(Arel::Nodes::StringJoin) && !buckets[:stashed_join].empty?
1134
+ buckets[:join_node] << join_node
1135
+ else
1136
+ buckets[:leading_join] << join_node
1137
+ end
1138
+ end
1139
+
1140
+ joins.each do |join|
998
1141
  case join
999
- when String
1000
- :string_join
1001
1142
  when Hash, Symbol, Array
1002
- :association_join
1143
+ buckets[:association_join] << join
1003
1144
  when ActiveRecord::Associations::JoinDependency
1004
- :stashed_join
1145
+ buckets[:stashed_join] << join
1005
1146
  when Arel::Nodes::Join
1006
- :join_node
1147
+ buckets[:join_node] << join
1007
1148
  else
1008
1149
  raise "unknown class: %s" % join.class.name
1009
1150
  end
@@ -1013,31 +1154,21 @@ module ActiveRecord
1013
1154
  end
1014
1155
 
1015
1156
  def build_join_query(manager, buckets, join_type, aliases)
1016
- buckets.default = []
1017
-
1018
1157
  association_joins = buckets[:association_join]
1019
1158
  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) }
1159
+ leading_joins = buckets[:leading_join]
1160
+ join_nodes = buckets[:join_node]
1030
1161
 
1031
- manager.join_sources.concat(join_list)
1162
+ join_sources = manager.join_sources
1163
+ join_sources.concat(leading_joins) unless leading_joins.empty?
1032
1164
 
1033
- alias_tracker.aliases
1034
- end
1165
+ unless association_joins.empty? && stashed_joins.empty?
1166
+ alias_tracker = alias_tracker(leading_joins + join_nodes, aliases)
1167
+ join_dependency = construct_join_dependency(association_joins, join_type)
1168
+ join_sources.concat(join_dependency.join_constraints(stashed_joins, alias_tracker))
1169
+ end
1035
1170
 
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)) }
1171
+ join_sources.concat(join_nodes) unless join_nodes.empty?
1041
1172
  end
1042
1173
 
1043
1174
  def build_select(arel)
@@ -1052,11 +1183,14 @@ module ActiveRecord
1052
1183
 
1053
1184
  def arel_columns(columns)
1054
1185
  columns.flat_map do |field|
1055
- if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value
1056
- arel_attribute(field)
1057
- elsif Symbol === field
1058
- connection.quote_table_name(field.to_s)
1059
- elsif Proc === field
1186
+ case field
1187
+ when Symbol
1188
+ arel_column(field.to_s) do |attr_name|
1189
+ connection.quote_table_name(attr_name)
1190
+ end
1191
+ when String
1192
+ arel_column(field, &:itself)
1193
+ when Proc
1060
1194
  field.call
1061
1195
  else
1062
1196
  field
@@ -1064,6 +1198,21 @@ module ActiveRecord
1064
1198
  end
1065
1199
  end
1066
1200
 
1201
+ def arel_column(field)
1202
+ field = klass.attribute_aliases[field] || field
1203
+ from = from_clause.name || from_clause.value
1204
+
1205
+ if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1206
+ arel_attribute(field)
1207
+ else
1208
+ yield field
1209
+ end
1210
+ end
1211
+
1212
+ def table_name_matches?(from)
1213
+ /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
1214
+ end
1215
+
1067
1216
  def reverse_sql_order(order_query)
1068
1217
  if order_query.empty?
1069
1218
  return [arel_attribute(primary_key).desc] if primary_key
@@ -1079,7 +1228,7 @@ module ActiveRecord
1079
1228
  o.reverse
1080
1229
  when String
1081
1230
  if does_not_support_reverse?(o)
1082
- raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
1231
+ raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1083
1232
  end
1084
1233
  o.split(",").map! do |s|
1085
1234
  s.strip!
@@ -1099,7 +1248,7 @@ module ActiveRecord
1099
1248
  # Uses SQL function with multiple arguments.
1100
1249
  (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
1101
1250
  # Uses "nulls first" like construction.
1102
- /nulls (first|last)\Z/i.match?(order)
1251
+ /\bnulls\s+(?:first|last)\b/i.match?(order)
1103
1252
  end
1104
1253
 
1105
1254
  def build_order(arel)
@@ -1125,6 +1274,7 @@ module ActiveRecord
1125
1274
  end
1126
1275
 
1127
1276
  def preprocess_order_args(order_args)
1277
+ order_args.reject!(&:blank?)
1128
1278
  order_args.map! do |arg|
1129
1279
  klass.sanitize_sql_for_order(arg)
1130
1280
  end
@@ -1132,7 +1282,7 @@ module ActiveRecord
1132
1282
 
1133
1283
  @klass.disallow_raw_sql!(
1134
1284
  order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
1135
- permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
1285
+ permit: connection.column_name_with_order_matcher
1136
1286
  )
1137
1287
 
1138
1288
  validate_order_args(order_args)
@@ -1145,14 +1295,14 @@ module ActiveRecord
1145
1295
  order_args.map! do |arg|
1146
1296
  case arg
1147
1297
  when Symbol
1148
- arel_attribute(arg).asc
1298
+ order_column(arg.to_s).asc
1149
1299
  when Hash
1150
1300
  arg.map { |field, dir|
1151
1301
  case field
1152
1302
  when Arel::Nodes::SqlLiteral
1153
1303
  field.send(dir.downcase)
1154
1304
  else
1155
- arel_attribute(field).send(dir.downcase)
1305
+ order_column(field.to_s).send(dir.downcase)
1156
1306
  end
1157
1307
  }
1158
1308
  else
@@ -1161,6 +1311,16 @@ module ActiveRecord
1161
1311
  end.flatten!
1162
1312
  end
1163
1313
 
1314
+ def order_column(field)
1315
+ arel_column(field) do |attr_name|
1316
+ if attr_name == "count" && !group_values.empty?
1317
+ arel_attribute(attr_name)
1318
+ else
1319
+ Arel.sql(connection.quote_table_name(attr_name))
1320
+ end
1321
+ end
1322
+ end
1323
+
1164
1324
  # Checks to make sure that the arguments are not blank. Note that if some
1165
1325
  # blank-like object were initially passed into the query method, then this
1166
1326
  # method will not raise an error.
@@ -1187,7 +1347,8 @@ module ActiveRecord
1187
1347
  def structurally_incompatible_values_for_or(other)
1188
1348
  values = other.values
1189
1349
  STRUCTURAL_OR_METHODS.reject do |method|
1190
- get_value(method) == values.fetch(method, DEFAULT_VALUES[method])
1350
+ default = DEFAULT_VALUES[method]
1351
+ @values.fetch(method, default) == values.fetch(method, default)
1191
1352
  end
1192
1353
  end
1193
1354