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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +286 -6
- data/README.rdoc +3 -1
- data/lib/active_record.rb +0 -1
- data/lib/active_record/associations.rb +3 -2
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/builder/association.rb +14 -18
- data/lib/active_record/associations/builder/belongs_to.rb +5 -2
- data/lib/active_record/associations/builder/collection_association.rb +3 -13
- data/lib/active_record/associations/builder/has_many.rb +2 -0
- data/lib/active_record/associations/builder/has_one.rb +35 -1
- data/lib/active_record/associations/builder/singular_association.rb +2 -0
- data/lib/active_record/associations/collection_proxy.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/preloader.rb +11 -6
- data/lib/active_record/associations/preloader/association.rb +32 -30
- data/lib/active_record/associations/preloader/through_association.rb +48 -28
- data/lib/active_record/attribute_methods.rb +4 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +42 -14
- data/lib/active_record/attribute_methods/primary_key.rb +7 -15
- data/lib/active_record/attribute_methods/query.rb +2 -3
- data/lib/active_record/attribute_methods/read.rb +3 -9
- data/lib/active_record/attribute_methods/write.rb +6 -12
- data/lib/active_record/attributes.rb +13 -0
- data/lib/active_record/autosave_association.rb +13 -3
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +84 -61
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +10 -6
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -7
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +70 -14
- data/lib/active_record/connection_adapters/abstract_adapter.rb +56 -11
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -69
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -7
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +9 -6
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +6 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +5 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +34 -38
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +57 -27
- data/lib/active_record/connection_adapters/schema_cache.rb +32 -14
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +50 -112
- data/lib/active_record/connection_handling.rb +17 -10
- data/lib/active_record/core.rb +15 -20
- data/lib/active_record/database_configurations.rb +14 -14
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +12 -12
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +6 -0
- data/lib/active_record/errors.rb +1 -1
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/insert_all.rb +180 -0
- data/lib/active_record/integration.rb +13 -1
- data/lib/active_record/internal_metadata.rb +5 -1
- data/lib/active_record/locking/optimistic.rb +3 -4
- data/lib/active_record/log_subscriber.rb +1 -1
- data/lib/active_record/migration.rb +25 -18
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +10 -0
- data/lib/active_record/persistence.rb +206 -13
- data/lib/active_record/querying.rb +17 -12
- data/lib/active_record/railties/databases.rake +68 -6
- data/lib/active_record/reflection.rb +2 -2
- data/lib/active_record/relation.rb +98 -20
- data/lib/active_record/relation/calculations.rb +39 -39
- data/lib/active_record/relation/delegation.rb +22 -30
- data/lib/active_record/relation/finder_methods.rb +3 -9
- data/lib/active_record/relation/merger.rb +7 -16
- data/lib/active_record/relation/query_methods.rb +153 -38
- data/lib/active_record/relation/where_clause.rb +9 -5
- data/lib/active_record/sanitization.rb +3 -2
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping/default.rb +6 -7
- data/lib/active_record/scoping/named.rb +1 -1
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +3 -3
- data/lib/active_record/tasks/database_tasks.rb +36 -1
- data/lib/active_record/touch_later.rb +2 -2
- data/lib/active_record/transactions.rb +52 -41
- data/lib/active_record/validations/uniqueness.rb +3 -5
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/nodes/comment.rb +29 -0
- data/lib/arel/nodes/select_core.rb +16 -12
- data/lib/arel/nodes/unary.rb +1 -0
- data/lib/arel/nodes/values_list.rb +2 -17
- data/lib/arel/select_manager.rb +10 -10
- data/lib/arel/visitors/depth_first.rb +6 -1
- data/lib/arel/visitors/dot.rb +7 -2
- data/lib/arel/visitors/ibm_db.rb +13 -0
- data/lib/arel/visitors/informix.rb +6 -0
- data/lib/arel/visitors/mssql.rb +15 -1
- data/lib/arel/visitors/oracle12.rb +4 -5
- data/lib/arel/visitors/postgresql.rb +4 -10
- data/lib/arel/visitors/to_sql.rb +87 -108
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
- metadata +12 -11
- data/lib/active_record/collection_cache_key.rb +0 -53
- 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 ||=
|
43
|
-
|
44
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
1008
|
+
def construct_join_dependency(associations) # :nodoc:
|
1009
|
+
ActiveRecord::Associations::JoinDependency.new(
|
1010
|
+
klass, table, associations
|
1011
|
+
)
|
1012
|
+
end
|
917
1013
|
|
918
|
-
|
919
|
-
def
|
920
|
-
|
921
|
-
|
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
|
-
|
933
|
-
|
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
|
982
|
-
|
983
|
-
case
|
1086
|
+
def valid_association_list(associations)
|
1087
|
+
associations.each do |association|
|
1088
|
+
case association
|
984
1089
|
when Hash, Symbol, Array
|
985
|
-
|
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
|
1169
|
+
arel_column(field, &connection.method(:quote_table_name))
|
1059
1170
|
when String
|
1060
|
-
arel_column(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.
|
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}
|
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
|
-
|
1324
|
+
default = DEFAULT_VALUES[method]
|
1325
|
+
@values.fetch(method, default) == values.fetch(method, default)
|
1211
1326
|
end
|
1212
1327
|
end
|
1213
1328
|
|