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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +529 -10
- data/README.rdoc +3 -1
- data/lib/active_record.rb +7 -1
- data/lib/active_record/association_relation.rb +15 -6
- data/lib/active_record/associations.rb +4 -3
- data/lib/active_record/associations/association.rb +27 -2
- 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 +5 -15
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
- 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_association.rb +5 -6
- data/lib/active_record/associations/collection_proxy.rb +13 -42
- data/lib/active_record/associations/has_many_association.rb +1 -9
- data/lib/active_record/associations/has_many_through_association.rb +4 -11
- data/lib/active_record/associations/join_dependency.rb +14 -9
- data/lib/active_record/associations/join_dependency/join_association.rb +21 -7
- data/lib/active_record/associations/preloader.rb +12 -7
- data/lib/active_record/associations/preloader/association.rb +37 -34
- data/lib/active_record/associations/preloader/through_association.rb +48 -39
- data/lib/active_record/attribute_methods.rb +3 -53
- data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
- data/lib/active_record/attribute_methods/dirty.rb +47 -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 +21 -7
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/callbacks.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +134 -23
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +105 -70
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +12 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +63 -6
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +5 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +51 -40
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +95 -30
- data/lib/active_record/connection_adapters/abstract/transaction.rb +17 -6
- data/lib/active_record/connection_adapters/abstract_adapter.rb +115 -35
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +106 -138
- data/lib/active_record/connection_adapters/column.rb +17 -13
- data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +3 -3
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +48 -8
- data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +66 -5
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +18 -5
- data/lib/active_record/connection_adapters/postgresql/column.rb +17 -30
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -3
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +98 -89
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +47 -63
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +23 -27
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +95 -24
- 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 +120 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +73 -118
- data/lib/active_record/connection_handling.rb +40 -17
- data/lib/active_record/core.rb +35 -24
- data/lib/active_record/database_configurations.rb +99 -50
- data/lib/active_record/database_configurations/hash_config.rb +11 -11
- data/lib/active_record/database_configurations/url_config.rb +21 -16
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/enum.rb +15 -0
- data/lib/active_record/errors.rb +18 -13
- data/lib/active_record/fixtures.rb +11 -6
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +179 -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/middleware/database_selector.rb +75 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
- data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
- data/lib/active_record/migration.rb +62 -44
- data/lib/active_record/migration/command_recorder.rb +28 -14
- data/lib/active_record/migration/compatibility.rb +72 -63
- data/lib/active_record/model_schema.rb +3 -0
- data/lib/active_record/persistence.rb +212 -19
- data/lib/active_record/querying.rb +18 -14
- data/lib/active_record/railtie.rb +9 -1
- data/lib/active_record/railties/collection_cache_association_loading.rb +3 -3
- data/lib/active_record/railties/databases.rake +124 -25
- data/lib/active_record/reflection.rb +18 -32
- data/lib/active_record/relation.rb +185 -35
- data/lib/active_record/relation/calculations.rb +40 -44
- data/lib/active_record/relation/delegation.rb +23 -31
- data/lib/active_record/relation/finder_methods.rb +23 -14
- data/lib/active_record/relation/merger.rb +11 -16
- data/lib/active_record/relation/query_attribute.rb +5 -3
- data/lib/active_record/relation/query_methods.rb +230 -69
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/relation/where_clause.rb +10 -10
- data/lib/active_record/sanitization.rb +33 -4
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +10 -1
- data/lib/active_record/schema_migration.rb +1 -1
- data/lib/active_record/scoping.rb +6 -7
- data/lib/active_record/scoping/default.rb +7 -15
- data/lib/active_record/scoping/named.rb +10 -2
- data/lib/active_record/statement_cache.rb +2 -2
- data/lib/active_record/store.rb +48 -0
- data/lib/active_record/table_metadata.rb +9 -13
- data/lib/active_record/tasks/database_tasks.rb +109 -6
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
- data/lib/active_record/test_databases.rb +1 -16
- data/lib/active_record/test_fixtures.rb +2 -2
- data/lib/active_record/timestamp.rb +35 -19
- data/lib/active_record/touch_later.rb +4 -2
- data/lib/active_record/transactions.rb +56 -46
- data/lib/active_record/type_caster/connection.rb +16 -10
- data/lib/active_record/validations.rb +1 -0
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/arel.rb +18 -4
- data/lib/arel/insert_manager.rb +3 -3
- data/lib/arel/nodes.rb +2 -1
- data/lib/arel/nodes/and.rb +1 -1
- data/lib/arel/nodes/case.rb +1 -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 +7 -2
- 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 +107 -131
- data/lib/arel/visitors/visitor.rb +9 -5
- 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 +19 -12
- 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
|
@@ -132,7 +124,7 @@ module ActiveRecord
|
|
132
124
|
|
133
125
|
private
|
134
126
|
def respond_to_missing?(method, _)
|
135
|
-
super || @klass.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
|
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.
|
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
|
-
|
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(
|
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 && !
|
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
|
-
|
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(
|
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
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
22
|
-
|
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
|
-
|
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)
|
@@ -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
|
-
|
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, join_type) # :nodoc:
|
1009
|
+
ActiveRecord::Associations::JoinDependency.new(
|
1010
|
+
klass, table, associations, join_type
|
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,32 +1083,68 @@ module ActiveRecord
|
|
978
1083
|
end
|
979
1084
|
end
|
980
1085
|
|
981
|
-
def
|
982
|
-
|
983
|
-
|
1086
|
+
def select_association_list(associations)
|
1087
|
+
result = []
|
1088
|
+
associations.each do |association|
|
1089
|
+
case association
|
984
1090
|
when Hash, Symbol, Array
|
985
|
-
|
986
|
-
when ActiveRecord::Associations::JoinDependency
|
987
|
-
:stashed_join
|
1091
|
+
result << association
|
988
1092
|
else
|
989
|
-
|
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 =
|
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
|
-
|
1021
|
-
|
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
|
1162
|
+
join_sources = manager.join_sources
|
1163
|
+
join_sources.concat(leading_joins) unless leading_joins.empty?
|
1032
1164
|
|
1033
|
-
|
1034
|
-
|
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
|
-
|
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
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
1059
|
-
|
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}
|
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
|
-
|
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:
|
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
|
-
|
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
|
-
|
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
|
-
|
1350
|
+
default = DEFAULT_VALUES[method]
|
1351
|
+
@values.fetch(method, default) == values.fetch(method, default)
|
1191
1352
|
end
|
1192
1353
|
end
|
1193
1354
|
|