activerecord 6.0.0.beta3 → 6.0.0.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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +286 -6
  3. data/README.rdoc +3 -1
  4. data/lib/active_record.rb +0 -1
  5. data/lib/active_record/associations.rb +3 -2
  6. data/lib/active_record/associations/association.rb +1 -1
  7. data/lib/active_record/associations/builder/association.rb +14 -18
  8. data/lib/active_record/associations/builder/belongs_to.rb +5 -2
  9. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  10. data/lib/active_record/associations/builder/has_many.rb +2 -0
  11. data/lib/active_record/associations/builder/has_one.rb +35 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  13. data/lib/active_record/associations/collection_proxy.rb +1 -1
  14. data/lib/active_record/associations/has_many_through_association.rb +4 -11
  15. data/lib/active_record/associations/preloader.rb +11 -6
  16. data/lib/active_record/associations/preloader/association.rb +32 -30
  17. data/lib/active_record/associations/preloader/through_association.rb +48 -28
  18. data/lib/active_record/attribute_methods.rb +4 -3
  19. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  20. data/lib/active_record/attribute_methods/dirty.rb +42 -14
  21. data/lib/active_record/attribute_methods/primary_key.rb +7 -15
  22. data/lib/active_record/attribute_methods/query.rb +2 -3
  23. data/lib/active_record/attribute_methods/read.rb +3 -9
  24. data/lib/active_record/attribute_methods/write.rb +6 -12
  25. data/lib/active_record/attributes.rb +13 -0
  26. data/lib/active_record/autosave_association.rb +13 -3
  27. data/lib/active_record/base.rb +0 -1
  28. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -0
  29. data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
  30. data/lib/active_record/connection_adapters/abstract/database_statements.rb +84 -61
  31. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  32. data/lib/active_record/connection_adapters/abstract/quoting.rb +10 -6
  33. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +70 -14
  35. data/lib/active_record/connection_adapters/abstract_adapter.rb +56 -11
  36. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -69
  37. data/lib/active_record/connection_adapters/column.rb +17 -13
  38. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
  39. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +9 -6
  41. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  42. data/lib/active_record/connection_adapters/mysql2_adapter.rb +6 -2
  43. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
  44. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
  45. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
  46. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
  47. data/lib/active_record/connection_adapters/postgresql_adapter.rb +57 -27
  48. data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
  49. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  50. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  51. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
  52. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +50 -112
  53. data/lib/active_record/connection_handling.rb +17 -10
  54. data/lib/active_record/core.rb +15 -20
  55. data/lib/active_record/database_configurations.rb +14 -14
  56. data/lib/active_record/database_configurations/hash_config.rb +11 -11
  57. data/lib/active_record/database_configurations/url_config.rb +12 -12
  58. data/lib/active_record/dynamic_matchers.rb +1 -1
  59. data/lib/active_record/enum.rb +6 -0
  60. data/lib/active_record/errors.rb +1 -1
  61. data/lib/active_record/gem_version.rb +1 -1
  62. data/lib/active_record/insert_all.rb +180 -0
  63. data/lib/active_record/integration.rb +13 -1
  64. data/lib/active_record/internal_metadata.rb +5 -1
  65. data/lib/active_record/locking/optimistic.rb +3 -4
  66. data/lib/active_record/log_subscriber.rb +1 -1
  67. data/lib/active_record/migration.rb +25 -18
  68. data/lib/active_record/migration/command_recorder.rb +28 -14
  69. data/lib/active_record/migration/compatibility.rb +10 -0
  70. data/lib/active_record/persistence.rb +206 -13
  71. data/lib/active_record/querying.rb +17 -12
  72. data/lib/active_record/railties/databases.rake +68 -6
  73. data/lib/active_record/reflection.rb +2 -2
  74. data/lib/active_record/relation.rb +98 -20
  75. data/lib/active_record/relation/calculations.rb +39 -39
  76. data/lib/active_record/relation/delegation.rb +22 -30
  77. data/lib/active_record/relation/finder_methods.rb +3 -9
  78. data/lib/active_record/relation/merger.rb +7 -16
  79. data/lib/active_record/relation/query_methods.rb +153 -38
  80. data/lib/active_record/relation/where_clause.rb +9 -5
  81. data/lib/active_record/sanitization.rb +3 -2
  82. data/lib/active_record/schema_dumper.rb +5 -0
  83. data/lib/active_record/schema_migration.rb +1 -1
  84. data/lib/active_record/scoping/default.rb +6 -7
  85. data/lib/active_record/scoping/named.rb +1 -1
  86. data/lib/active_record/statement_cache.rb +2 -2
  87. data/lib/active_record/store.rb +48 -0
  88. data/lib/active_record/table_metadata.rb +3 -3
  89. data/lib/active_record/tasks/database_tasks.rb +36 -1
  90. data/lib/active_record/touch_later.rb +2 -2
  91. data/lib/active_record/transactions.rb +52 -41
  92. data/lib/active_record/validations/uniqueness.rb +3 -5
  93. data/lib/arel/insert_manager.rb +3 -3
  94. data/lib/arel/nodes.rb +2 -1
  95. data/lib/arel/nodes/comment.rb +29 -0
  96. data/lib/arel/nodes/select_core.rb +16 -12
  97. data/lib/arel/nodes/unary.rb +1 -0
  98. data/lib/arel/nodes/values_list.rb +2 -17
  99. data/lib/arel/select_manager.rb +10 -10
  100. data/lib/arel/visitors/depth_first.rb +6 -1
  101. data/lib/arel/visitors/dot.rb +7 -2
  102. data/lib/arel/visitors/ibm_db.rb +13 -0
  103. data/lib/arel/visitors/informix.rb +6 -0
  104. data/lib/arel/visitors/mssql.rb +15 -1
  105. data/lib/arel/visitors/oracle12.rb +4 -5
  106. data/lib/arel/visitors/postgresql.rb +4 -10
  107. data/lib/arel/visitors/to_sql.rb +87 -108
  108. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
  109. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  110. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  111. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  112. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  113. metadata +12 -11
  114. data/lib/active_record/collection_cache_key.rb +0 -53
  115. 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
@@ -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
@@ -370,12 +370,6 @@ 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
374
  join_dependency = construct_join_dependency(eager_load_values + includes_values)
381
375
  relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
@@ -117,16 +117,14 @@ 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(associations)
127
+ relation.joins!(join_dependency, *others)
130
128
  end
131
129
  end
132
130
 
@@ -136,16 +134,9 @@ module ActiveRecord
136
134
  if other.klass == relation.klass
137
135
  relation.left_outer_joins!(*other.left_outer_joins_values)
138
136
  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)
137
+ associations = other.left_outer_joins_values
138
+ join_dependency = other.construct_join_dependency(associations)
139
+ relation.joins!(join_dependency)
149
140
  end
150
141
  end
151
142
 
@@ -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)
@@ -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) # :nodoc:
1009
+ ActiveRecord::Associations::JoinDependency.new(
1010
+ klass, table, associations
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,22 +1083,28 @@ 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 valid_association_list(associations)
1087
+ associations.each do |association|
1088
+ case association
984
1089
  when Hash, Symbol, Array
985
- :association_join
986
- when ActiveRecord::Associations::JoinDependency
987
- :stashed_join
1090
+ # valid
988
1091
  else
989
1092
  raise ArgumentError, "only Hash, Symbol and Array are allowed"
990
1093
  end
991
1094
  end
1095
+ end
992
1096
 
1097
+ def build_left_outer_joins(manager, outer_joins, aliases)
1098
+ buckets = { association_join: valid_association_list(outer_joins) }
993
1099
  build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases)
994
1100
  end
995
1101
 
996
1102
  def build_joins(manager, joins, aliases)
1103
+ unless left_outer_joins_values.empty?
1104
+ left_joins = valid_association_list(left_outer_joins_values.flatten)
1105
+ joins << construct_join_dependency(left_joins)
1106
+ end
1107
+
997
1108
  buckets = joins.group_by do |join|
998
1109
  case join
999
1110
  when String
@@ -1055,9 +1166,9 @@ module ActiveRecord
1055
1166
  case field
1056
1167
  when Symbol
1057
1168
  field = field.to_s
1058
- arel_column(field) { connection.quote_table_name(field) }
1169
+ arel_column(field, &connection.method(:quote_table_name))
1059
1170
  when String
1060
- arel_column(field) { field }
1171
+ arel_column(field, &:itself)
1061
1172
  when Proc
1062
1173
  field.call
1063
1174
  else
@@ -1067,17 +1178,20 @@ module ActiveRecord
1067
1178
  end
1068
1179
 
1069
1180
  def arel_column(field)
1070
- field = klass.attribute_alias(field) if klass.attribute_alias?(field)
1181
+ field = klass.attribute_aliases[field] || field
1071
1182
  from = from_clause.name || from_clause.value
1072
1183
 
1073
- if klass.columns_hash.key?(field) &&
1074
- (!from || from == table.name || from == connection.quote_table_name(table.name))
1184
+ if klass.columns_hash.key?(field) && (!from || table_name_matches?(from))
1075
1185
  arel_attribute(field)
1076
1186
  else
1077
- yield
1187
+ yield field
1078
1188
  end
1079
1189
  end
1080
1190
 
1191
+ def table_name_matches?(from)
1192
+ /(?:\A|(?<!FROM)\s)(?:\b#{table.name}\b|#{connection.quote_table_name(table.name)})(?!\.)/i.match?(from.to_s)
1193
+ end
1194
+
1081
1195
  def reverse_sql_order(order_query)
1082
1196
  if order_query.empty?
1083
1197
  return [arel_attribute(primary_key).desc] if primary_key
@@ -1093,7 +1207,7 @@ module ActiveRecord
1093
1207
  o.reverse
1094
1208
  when String
1095
1209
  if does_not_support_reverse?(o)
1096
- raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically"
1210
+ raise IrreversibleOrderError, "Order #{o.inspect} cannot be reversed automatically"
1097
1211
  end
1098
1212
  o.split(",").map! do |s|
1099
1213
  s.strip!
@@ -1207,7 +1321,8 @@ module ActiveRecord
1207
1321
  def structurally_incompatible_values_for_or(other)
1208
1322
  values = other.values
1209
1323
  STRUCTURAL_OR_METHODS.reject do |method|
1210
- get_value(method) == values.fetch(method, DEFAULT_VALUES[method])
1324
+ default = DEFAULT_VALUES[method]
1325
+ @values.fetch(method, default) == values.fetch(method, default)
1211
1326
  end
1212
1327
  end
1213
1328