activerecord 6.1.0 → 6.1.3
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 +195 -15
- data/README.rdoc +1 -1
- data/lib/active_record/aggregations.rb +4 -4
- data/lib/active_record/association_relation.rb +10 -0
- data/lib/active_record/associations/association.rb +7 -7
- data/lib/active_record/associations/association_scope.rb +7 -5
- data/lib/active_record/associations/belongs_to_association.rb +7 -3
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +23 -2
- data/lib/active_record/associations/builder/belongs_to.rb +2 -2
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +8 -7
- data/lib/active_record/associations/join_dependency.rb +1 -1
- data/lib/active_record/associations.rb +6 -2
- data/lib/active_record/attributes.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +4 -4
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +7 -1
- data/lib/active_record/connection_adapters/abstract_adapter.rb +7 -8
- data/lib/active_record/connection_adapters/mysql/quoting.rb +17 -2
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +4 -1
- data/lib/active_record/connection_adapters/pool_config.rb +13 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +2 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
- data/lib/active_record/connection_handling.rb +20 -12
- data/lib/active_record/core.rb +42 -24
- data/lib/active_record/database_configurations/url_config.rb +1 -1
- data/lib/active_record/enum.rb +48 -28
- data/lib/active_record/fixtures.rb +5 -2
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +14 -4
- data/lib/active_record/log_subscriber.rb +3 -2
- data/lib/active_record/migration/compatibility.rb +2 -1
- data/lib/active_record/migration.rb +1 -1
- data/lib/active_record/model_schema.rb +4 -4
- data/lib/active_record/railties/console_sandbox.rb +2 -4
- data/lib/active_record/railties/databases.rake +13 -7
- data/lib/active_record/reflection.rb +1 -1
- data/lib/active_record/relation/finder_methods.rb +1 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +3 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +9 -5
- data/lib/active_record/relation/predicate_builder.rb +5 -6
- data/lib/active_record/relation/query_methods.rb +8 -5
- data/lib/active_record/relation/where_clause.rb +5 -5
- data/lib/active_record/relation.rb +1 -2
- data/lib/active_record/signed_id.rb +1 -1
- data/lib/active_record/table_metadata.rb +6 -3
- data/lib/active_record/tasks/database_tasks.rb +1 -0
- data/lib/active_record/transactions.rb +4 -2
- metadata +13 -13
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/time_with_zone"
|
4
|
+
|
3
5
|
module ActiveRecord
|
4
6
|
module ConnectionAdapters
|
5
7
|
module MySQL
|
@@ -69,10 +71,23 @@ module ActiveRecord
|
|
69
71
|
private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
|
70
72
|
|
71
73
|
private
|
74
|
+
# Override +_type_cast+ we pass to mysql2 Date and Time objects instead
|
75
|
+
# of Strings since mysql2 is able to handle those classes more efficiently.
|
72
76
|
def _type_cast(value)
|
73
77
|
case value
|
74
|
-
when
|
75
|
-
|
78
|
+
when ActiveSupport::TimeWithZone
|
79
|
+
# We need to check explicitly for ActiveSupport::TimeWithZone because
|
80
|
+
# we need to transform it to Time objects but we don't want to
|
81
|
+
# transform Time objects to themselves.
|
82
|
+
if ActiveRecord::Base.default_timezone == :utc
|
83
|
+
value.getutc
|
84
|
+
else
|
85
|
+
value.getlocal
|
86
|
+
end
|
87
|
+
when Date, Time
|
88
|
+
value
|
89
|
+
else
|
90
|
+
super
|
76
91
|
end
|
77
92
|
end
|
78
93
|
end
|
@@ -79,7 +79,10 @@ module ActiveRecord
|
|
79
79
|
" WHERE table_schema = #{scope[:schema]}" \
|
80
80
|
" AND table_name = #{scope[:name]}" \
|
81
81
|
" AND column_name = #{column_name}"
|
82
|
-
|
82
|
+
# Calling .inspect leads into issues with the query result
|
83
|
+
# which already returns escaped quotes.
|
84
|
+
# We remove the escape sequence from the result in order to deal with double escaping issues.
|
85
|
+
@connection.query_value(sql, "SCHEMA").gsub("\\'", "'").inspect
|
83
86
|
end
|
84
87
|
end
|
85
88
|
end
|
@@ -5,7 +5,7 @@ module ActiveRecord
|
|
5
5
|
class PoolConfig # :nodoc:
|
6
6
|
include Mutex_m
|
7
7
|
|
8
|
-
attr_reader :db_config, :
|
8
|
+
attr_reader :db_config, :connection_klass
|
9
9
|
attr_accessor :schema_cache
|
10
10
|
|
11
11
|
INSTANCES = ObjectSpace::WeakMap.new
|
@@ -17,14 +17,24 @@ module ActiveRecord
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
def initialize(
|
20
|
+
def initialize(connection_klass, db_config)
|
21
21
|
super()
|
22
|
-
@
|
22
|
+
@connection_klass = connection_klass
|
23
23
|
@db_config = db_config
|
24
24
|
@pool = nil
|
25
25
|
INSTANCES[self] = self
|
26
26
|
end
|
27
27
|
|
28
|
+
def connection_specification_name
|
29
|
+
if connection_klass.is_a?(String)
|
30
|
+
connection_klass
|
31
|
+
elsif connection_klass.primary_class?
|
32
|
+
"ActiveRecord::Base"
|
33
|
+
else
|
34
|
+
connection_klass.name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
28
38
|
def disconnect!
|
29
39
|
ActiveSupport::ForkTracker.check!
|
30
40
|
|
@@ -57,7 +57,7 @@ module ActiveRecord
|
|
57
57
|
ftype = result.ftype i
|
58
58
|
fmod = result.fmod i
|
59
59
|
case type = get_oid_type(ftype, fmod, fname)
|
60
|
-
when Type::Integer, Type::Float,
|
60
|
+
when Type::Integer, Type::Float, OID::Decimal, Type::String, Type::DateTime, Type::Boolean
|
61
61
|
# skip if a column has already been type casted by pg decoders
|
62
62
|
else types[fname] = type
|
63
63
|
end
|
@@ -26,9 +26,9 @@ module ActiveRecord
|
|
26
26
|
|
27
27
|
value = value.sub(/^\((.+)\)$/, '-\1') # (4)
|
28
28
|
case value
|
29
|
-
when /^-?\D
|
29
|
+
when /^-?\D*+[\d,]+\.\d{2}$/ # (1)
|
30
30
|
value.gsub!(/[^-\d.]/, "")
|
31
|
-
when /^-?\D
|
31
|
+
when /^-?\D*+[\d.]+,\d{2}$/ # (2)
|
32
32
|
value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
|
33
33
|
end
|
34
34
|
|
@@ -227,11 +227,7 @@ module ActiveRecord
|
|
227
227
|
end
|
228
228
|
|
229
229
|
def next_key
|
230
|
-
"a#{@counter
|
231
|
-
end
|
232
|
-
|
233
|
-
def []=(sql, key)
|
234
|
-
super.tap { @counter += 1 }
|
230
|
+
"a#{@counter += 1}"
|
235
231
|
end
|
236
232
|
|
237
233
|
private
|
@@ -649,9 +645,7 @@ module ActiveRecord
|
|
649
645
|
raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
|
650
646
|
end
|
651
647
|
|
652
|
-
if without_prepared_statement?(binds)
|
653
|
-
result = exec_no_cache(sql, name, [])
|
654
|
-
elsif !prepare
|
648
|
+
if !prepare || without_prepared_statement?(binds)
|
655
649
|
result = exec_no_cache(sql, name, binds)
|
656
650
|
else
|
657
651
|
result = exec_cache(sql, name, binds)
|
@@ -271,7 +271,7 @@ module ActiveRecord
|
|
271
271
|
def change_column(table_name, column_name, type, **options) #:nodoc:
|
272
272
|
alter_table(table_name) do |definition|
|
273
273
|
definition[column_name].instance_eval do
|
274
|
-
self.type = type
|
274
|
+
self.type = aliased_types(type.to_s, type)
|
275
275
|
self.options.merge!(options)
|
276
276
|
end
|
277
277
|
end
|
@@ -91,6 +91,7 @@ module ActiveRecord
|
|
91
91
|
db_config, owner_name = resolve_config_for_connection(database_key)
|
92
92
|
handler = lookup_connection_handler(role.to_sym)
|
93
93
|
|
94
|
+
self.connection_class = true
|
94
95
|
connections << handler.establish_connection(db_config, owner_name: owner_name, role: role)
|
95
96
|
end
|
96
97
|
|
@@ -99,6 +100,7 @@ module ActiveRecord
|
|
99
100
|
db_config, owner_name = resolve_config_for_connection(database_key)
|
100
101
|
handler = lookup_connection_handler(role.to_sym)
|
101
102
|
|
103
|
+
self.connection_class = true
|
102
104
|
connections << handler.establish_connection(db_config, owner_name: owner_name, role: role, shard: shard.to_sym)
|
103
105
|
end
|
104
106
|
end
|
@@ -112,7 +114,7 @@ module ActiveRecord
|
|
112
114
|
#
|
113
115
|
# If only a role is passed, Active Record will look up the connection
|
114
116
|
# based on the requested role. If a non-established role is requested
|
115
|
-
# an
|
117
|
+
# an +ActiveRecord::ConnectionNotEstablished+ error will be raised:
|
116
118
|
#
|
117
119
|
# ActiveRecord::Base.connected_to(role: :writing) do
|
118
120
|
# Dog.create! # creates dog using dog writing connection
|
@@ -123,7 +125,7 @@ module ActiveRecord
|
|
123
125
|
# end
|
124
126
|
#
|
125
127
|
# When swapping to a shard, the role must be passed as well. If a non-existent
|
126
|
-
# shard is passed, an
|
128
|
+
# shard is passed, an +ActiveRecord::ConnectionNotEstablished+ error will be
|
127
129
|
# raised.
|
128
130
|
#
|
129
131
|
# When a shard and role is passed, Active Record will first lookup the role,
|
@@ -143,6 +145,10 @@ module ActiveRecord
|
|
143
145
|
if self != Base && !abstract_class
|
144
146
|
raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes."
|
145
147
|
end
|
148
|
+
|
149
|
+
if name != connection_specification_name && !primary_class?
|
150
|
+
raise NotImplementedError, "calling `connected_to` is only allowed on the abstract class that established the connection."
|
151
|
+
end
|
146
152
|
end
|
147
153
|
|
148
154
|
if database && (role || shard)
|
@@ -172,20 +178,22 @@ module ActiveRecord
|
|
172
178
|
end
|
173
179
|
end
|
174
180
|
|
175
|
-
# Connects a role and/or shard to the provided connection names. Optionally
|
176
|
-
# can be passed to block writes on a connection.
|
177
|
-
#
|
181
|
+
# Connects a role and/or shard to the provided connection names. Optionally +prevent_writes+
|
182
|
+
# can be passed to block writes on a connection. +reading+ will automatically set
|
183
|
+
# +prevent_writes+ to true.
|
178
184
|
#
|
179
|
-
#
|
185
|
+
# +connected_to_many+ is an alternative to deeply nested +connected_to+ blocks.
|
180
186
|
#
|
181
187
|
# Usage:
|
182
188
|
#
|
183
|
-
# ActiveRecord::Base.
|
189
|
+
# ActiveRecord::Base.connected_to_many(AnimalsRecord, MealsRecord, role: :reading) do
|
184
190
|
# Dog.first # Read from animals replica
|
185
191
|
# Dinner.first # Read from meals replica
|
186
192
|
# Person.first # Read from primary writer
|
187
193
|
# end
|
188
|
-
def connected_to_many(classes, role:, shard: nil, prevent_writes: false)
|
194
|
+
def connected_to_many(*classes, role:, shard: nil, prevent_writes: false)
|
195
|
+
classes = classes.flatten
|
196
|
+
|
189
197
|
if legacy_connection_handling
|
190
198
|
raise NotImplementedError, "connected_to_many is not available with legacy connection handling"
|
191
199
|
end
|
@@ -208,7 +216,7 @@ module ActiveRecord
|
|
208
216
|
# being used. For example, when booting a console in readonly mode.
|
209
217
|
#
|
210
218
|
# It is not recommended to use this method in a request since it
|
211
|
-
# does not yield to a block like
|
219
|
+
# does not yield to a block like +connected_to+.
|
212
220
|
def connecting_to(role: default_role, shard: default_shard, prevent_writes: false)
|
213
221
|
if legacy_connection_handling
|
214
222
|
raise NotImplementedError, "`connecting_to` is not available with `legacy_connection_handling`."
|
@@ -222,13 +230,13 @@ module ActiveRecord
|
|
222
230
|
# Prevent writing to the database regardless of role.
|
223
231
|
#
|
224
232
|
# In some cases you may want to prevent writes to the database
|
225
|
-
# even if you are on a database that can write.
|
233
|
+
# even if you are on a database that can write. +while_preventing_writes+
|
226
234
|
# will prevent writes to the database for the duration of the block.
|
227
235
|
#
|
228
236
|
# This method does not provide the same protection as a readonly
|
229
237
|
# user and is meant to be a safeguard against accidental writes.
|
230
238
|
#
|
231
|
-
# See
|
239
|
+
# See +READ_QUERY+ for the queries that are blocked by this
|
232
240
|
# method.
|
233
241
|
def while_preventing_writes(enabled = true, &block)
|
234
242
|
if legacy_connection_handling
|
@@ -357,7 +365,7 @@ module ActiveRecord
|
|
357
365
|
self.connection_specification_name = owner_name
|
358
366
|
|
359
367
|
db_config = Base.configurations.resolve(config_or_env)
|
360
|
-
[db_config,
|
368
|
+
[db_config, self]
|
361
369
|
end
|
362
370
|
|
363
371
|
def with_handler(handler_key, &blk)
|
data/lib/active_record/core.rb
CHANGED
@@ -196,7 +196,7 @@ module ActiveRecord
|
|
196
196
|
else
|
197
197
|
connected_to_stack.reverse_each do |hash|
|
198
198
|
return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
|
199
|
-
return hash[:role] if hash[:role] && hash[:klasses].include?(
|
199
|
+
return hash[:role] if hash[:role] && hash[:klasses].include?(connection_classes)
|
200
200
|
end
|
201
201
|
|
202
202
|
default_role
|
@@ -215,7 +215,7 @@ module ActiveRecord
|
|
215
215
|
def self.current_shard
|
216
216
|
connected_to_stack.reverse_each do |hash|
|
217
217
|
return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
|
218
|
-
return hash[:shard] if hash[:shard] && hash[:klasses].include?(
|
218
|
+
return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_classes)
|
219
219
|
end
|
220
220
|
|
221
221
|
default_shard
|
@@ -237,7 +237,7 @@ module ActiveRecord
|
|
237
237
|
else
|
238
238
|
connected_to_stack.reverse_each do |hash|
|
239
239
|
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
|
240
|
-
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(
|
240
|
+
return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_classes)
|
241
241
|
end
|
242
242
|
|
243
243
|
false
|
@@ -254,11 +254,23 @@ module ActiveRecord
|
|
254
254
|
end
|
255
255
|
end
|
256
256
|
|
257
|
-
def self.
|
257
|
+
def self.connection_class=(b) # :nodoc:
|
258
|
+
@connection_class = b
|
259
|
+
end
|
260
|
+
|
261
|
+
def self.connection_class # :nodoc
|
262
|
+
@connection_class ||= false
|
263
|
+
end
|
264
|
+
|
265
|
+
def self.connection_class? # :nodoc:
|
266
|
+
self.connection_class
|
267
|
+
end
|
268
|
+
|
269
|
+
def self.connection_classes # :nodoc:
|
258
270
|
klass = self
|
259
271
|
|
260
272
|
until klass == Base
|
261
|
-
break if klass.
|
273
|
+
break if klass.connection_class?
|
262
274
|
klass = klass.superclass
|
263
275
|
end
|
264
276
|
|
@@ -277,14 +289,14 @@ module ActiveRecord
|
|
277
289
|
self.default_role = writing_role
|
278
290
|
self.default_shard = :default
|
279
291
|
|
280
|
-
def self.strict_loading_violation!(owner:,
|
292
|
+
def self.strict_loading_violation!(owner:, reflection:) # :nodoc:
|
281
293
|
case action_on_strict_loading_violation
|
282
294
|
when :raise
|
283
|
-
message = "`#{
|
295
|
+
message = "`#{owner}` is marked for strict_loading. The `#{reflection.klass}` association named `:#{reflection.name}` cannot be lazily loaded."
|
284
296
|
raise ActiveRecord::StrictLoadingViolationError.new(message)
|
285
297
|
when :log
|
286
298
|
name = "strict_loading_violation.active_record"
|
287
|
-
ActiveSupport::Notifications.instrument(name, owner: owner,
|
299
|
+
ActiveSupport::Notifications.instrument(name, owner: owner, reflection: reflection)
|
288
300
|
end
|
289
301
|
end
|
290
302
|
end
|
@@ -332,31 +344,37 @@ module ActiveRecord
|
|
332
344
|
hash = args.first
|
333
345
|
return super unless Hash === hash
|
334
346
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
347
|
+
hash = hash.each_with_object({}) do |(key, value), h|
|
348
|
+
key = key.to_s
|
349
|
+
key = attribute_aliases[key] || key
|
350
|
+
|
351
|
+
return super if reflect_on_aggregation(key)
|
352
|
+
|
353
|
+
reflection = _reflect_on_association(key)
|
354
|
+
|
355
|
+
if !reflection
|
356
|
+
value = value.id if value.respond_to?(:id)
|
357
|
+
elsif reflection.belongs_to? && !reflection.polymorphic?
|
358
|
+
key = reflection.join_foreign_key
|
359
|
+
pkey = reflection.join_primary_key
|
360
|
+
value = value.public_send(pkey) if value.respond_to?(pkey)
|
348
361
|
end
|
349
|
-
end
|
350
362
|
|
351
|
-
|
363
|
+
if !columns_hash.key?(key) || StatementCache.unsupported_value?(value)
|
364
|
+
return super
|
365
|
+
end
|
366
|
+
|
367
|
+
h[key] = value
|
368
|
+
end
|
352
369
|
|
370
|
+
keys = hash.keys
|
353
371
|
statement = cached_find_by_statement(keys) { |params|
|
354
372
|
wheres = keys.index_with { params.bind }
|
355
373
|
where(wheres).limit(1)
|
356
374
|
}
|
357
375
|
|
358
376
|
begin
|
359
|
-
statement.execute(values, connection).first
|
377
|
+
statement.execute(hash.values, connection).first
|
360
378
|
rescue TypeError
|
361
379
|
raise ActiveRecord::StatementInvalid
|
362
380
|
end
|
@@ -42,7 +42,7 @@ module ActiveRecord
|
|
42
42
|
# Return a Hash that can be merged into the main config that represents
|
43
43
|
# the passed in url
|
44
44
|
def build_url_hash
|
45
|
-
if url.nil? || url.start_with?(
|
45
|
+
if url.nil? || %w(jdbc: http: https:).any? { |protocol| url.start_with?(protocol) }
|
46
46
|
{ url: url }
|
47
47
|
else
|
48
48
|
ConnectionUrlResolver.new(url).to_hash
|
data/lib/active_record/enum.rb
CHANGED
@@ -158,8 +158,6 @@ module ActiveRecord
|
|
158
158
|
end
|
159
159
|
|
160
160
|
def enum(definitions)
|
161
|
-
klass = self
|
162
|
-
|
163
161
|
enum_prefix = definitions.delete(:_prefix)
|
164
162
|
enum_suffix = definitions.delete(:_suffix)
|
165
163
|
enum_scopes = definitions.delete(:_scopes)
|
@@ -187,55 +185,77 @@ module ActiveRecord
|
|
187
185
|
EnumType.new(attr, enum_values, subtype)
|
188
186
|
end
|
189
187
|
|
188
|
+
value_method_names = []
|
190
189
|
_enum_methods_module.module_eval do
|
190
|
+
prefix = if enum_prefix == true
|
191
|
+
"#{name}_"
|
192
|
+
elsif enum_prefix
|
193
|
+
"#{enum_prefix}_"
|
194
|
+
end
|
195
|
+
|
196
|
+
suffix = if enum_suffix == true
|
197
|
+
"_#{name}"
|
198
|
+
elsif enum_suffix
|
199
|
+
"_#{enum_suffix}"
|
200
|
+
end
|
201
|
+
|
191
202
|
pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
|
192
|
-
value_method_names = []
|
193
203
|
pairs.each do |label, value|
|
194
|
-
if enum_prefix == true
|
195
|
-
prefix = "#{name}_"
|
196
|
-
elsif enum_prefix
|
197
|
-
prefix = "#{enum_prefix}_"
|
198
|
-
end
|
199
|
-
if enum_suffix == true
|
200
|
-
suffix = "_#{name}"
|
201
|
-
elsif enum_suffix
|
202
|
-
suffix = "_#{enum_suffix}"
|
203
|
-
end
|
204
|
-
|
205
|
-
method_friendly_label = label.to_s.gsub(/\W+/, "_")
|
206
|
-
value_method_name = "#{prefix}#{method_friendly_label}#{suffix}"
|
207
|
-
value_method_names << value_method_name
|
208
204
|
enum_values[label] = value
|
209
205
|
label = label.to_s
|
210
206
|
|
211
|
-
|
207
|
+
value_method_name = "#{prefix}#{label}#{suffix}"
|
208
|
+
value_method_names << value_method_name
|
209
|
+
define_enum_methods(name, value_method_name, value, enum_scopes)
|
210
|
+
|
211
|
+
method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
|
212
|
+
value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
|
213
|
+
|
214
|
+
if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
|
215
|
+
value_method_names << value_method_alias
|
216
|
+
define_enum_methods(name, value_method_alias, value, enum_scopes)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
detect_negative_enum_conditions!(value_method_names) if enum_scopes != false
|
221
|
+
enum_values.freeze
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
private
|
226
|
+
class EnumMethods < Module # :nodoc:
|
227
|
+
def initialize(klass)
|
228
|
+
@klass = klass
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
attr_reader :klass
|
233
|
+
|
234
|
+
def define_enum_methods(name, value_method_name, value, enum_scopes)
|
235
|
+
# def active?() status_for_database == 0 end
|
212
236
|
klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
|
213
|
-
define_method("#{value_method_name}?") {
|
237
|
+
define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
|
214
238
|
|
215
239
|
# def active!() update!(status: 0) end
|
216
240
|
klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
|
217
|
-
define_method("#{value_method_name}!") { update!(
|
241
|
+
define_method("#{value_method_name}!") { update!(name => value) }
|
218
242
|
|
219
243
|
# scope :active, -> { where(status: 0) }
|
220
244
|
# scope :not_active, -> { where.not(status: 0) }
|
221
245
|
if enum_scopes != false
|
222
246
|
klass.send(:detect_enum_conflict!, name, value_method_name, true)
|
223
|
-
klass.scope value_method_name, -> { where(
|
247
|
+
klass.scope value_method_name, -> { where(name => value) }
|
224
248
|
|
225
249
|
klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
|
226
|
-
klass.scope "not_#{value_method_name}", -> { where.not(
|
250
|
+
klass.scope "not_#{value_method_name}", -> { where.not(name => value) }
|
227
251
|
end
|
228
252
|
end
|
229
|
-
klass.send(:detect_negative_enum_conditions!, value_method_names) if enum_scopes != false
|
230
|
-
end
|
231
|
-
enum_values.freeze
|
232
253
|
end
|
233
|
-
|
254
|
+
private_constant :EnumMethods
|
234
255
|
|
235
|
-
private
|
236
256
|
def _enum_methods_module
|
237
257
|
@_enum_methods_module ||= begin
|
238
|
-
mod =
|
258
|
+
mod = EnumMethods.new(self)
|
239
259
|
include mod
|
240
260
|
mod
|
241
261
|
end
|
@@ -181,7 +181,7 @@ module ActiveRecord
|
|
181
181
|
# end
|
182
182
|
# end
|
183
183
|
#
|
184
|
-
# If you preload your test database with all fixture data (probably by running
|
184
|
+
# If you preload your test database with all fixture data (probably by running <tt>bin/rails db:fixtures:load</tt>)
|
185
185
|
# and use transactional tests, then you may omit all fixtures declarations in your test cases since
|
186
186
|
# all the data's already there and every case rolls back its changes.
|
187
187
|
#
|
@@ -773,9 +773,12 @@ module ActiveRecord
|
|
773
773
|
|
774
774
|
def find
|
775
775
|
raise FixtureClassNotFound, "No class attached to find." unless model_class
|
776
|
-
model_class.unscoped do
|
776
|
+
object = model_class.unscoped do
|
777
777
|
model_class.find(fixture[model_class.primary_key])
|
778
778
|
end
|
779
|
+
# Fixtures can't be eagerly loaded
|
780
|
+
object.instance_variable_set(:@strict_loading, false)
|
781
|
+
object
|
779
782
|
end
|
780
783
|
end
|
781
784
|
end
|
@@ -89,7 +89,9 @@ module ActiveRecord
|
|
89
89
|
|
90
90
|
begin
|
91
91
|
locking_column = self.class.locking_column
|
92
|
-
|
92
|
+
lock_attribute_was = @attributes[locking_column]
|
93
|
+
lock_value_for_database = _lock_value_for_database(locking_column)
|
94
|
+
|
93
95
|
attribute_names = attribute_names.dup if attribute_names.frozen?
|
94
96
|
attribute_names << locking_column
|
95
97
|
|
@@ -98,7 +100,7 @@ module ActiveRecord
|
|
98
100
|
affected_rows = self.class._update_record(
|
99
101
|
attributes_with_values(attribute_names),
|
100
102
|
@primary_key => id_in_database,
|
101
|
-
locking_column =>
|
103
|
+
locking_column => lock_value_for_database
|
102
104
|
)
|
103
105
|
|
104
106
|
if affected_rows != 1
|
@@ -109,7 +111,7 @@ module ActiveRecord
|
|
109
111
|
|
110
112
|
# If something went wrong, revert the locking_column value.
|
111
113
|
rescue Exception
|
112
|
-
|
114
|
+
@attributes[locking_column] = lock_attribute_was
|
113
115
|
raise
|
114
116
|
end
|
115
117
|
end
|
@@ -121,7 +123,7 @@ module ActiveRecord
|
|
121
123
|
|
122
124
|
affected_rows = self.class._delete_record(
|
123
125
|
@primary_key => id_in_database,
|
124
|
-
locking_column =>
|
126
|
+
locking_column => _lock_value_for_database(locking_column)
|
125
127
|
)
|
126
128
|
|
127
129
|
if affected_rows != 1
|
@@ -131,6 +133,14 @@ module ActiveRecord
|
|
131
133
|
affected_rows
|
132
134
|
end
|
133
135
|
|
136
|
+
def _lock_value_for_database(locking_column)
|
137
|
+
if will_save_change_to_attribute?(locking_column)
|
138
|
+
@attributes[locking_column].value_for_database
|
139
|
+
else
|
140
|
+
@attributes[locking_column].original_value_for_database
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
134
144
|
module ClassMethods
|
135
145
|
DEFAULT_LOCKING_COLUMN = "lock_version"
|
136
146
|
|
@@ -22,9 +22,10 @@ module ActiveRecord
|
|
22
22
|
def strict_loading_violation(event)
|
23
23
|
debug do
|
24
24
|
owner = event.payload[:owner]
|
25
|
-
association = event.payload[:
|
25
|
+
association = event.payload[:reflection].klass
|
26
|
+
name = event.payload[:reflection].name
|
26
27
|
|
27
|
-
color("Strict loading violation: #{
|
28
|
+
color("Strict loading violation: #{owner} is marked for strict loading. The #{association} association named :#{name} cannot be lazily loaded.", RED)
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
@@ -56,7 +56,8 @@ module ActiveRecord
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def add_reference(table_name, ref_name, **options)
|
59
|
-
ReferenceDefinition.new(ref_name, **options)
|
59
|
+
ReferenceDefinition.new(ref_name, **options)
|
60
|
+
.add_to(connection.update_table_definition(table_name, self))
|
60
61
|
end
|
61
62
|
alias :add_belongs_to :add_reference
|
62
63
|
|
@@ -320,7 +320,7 @@ module ActiveRecord
|
|
320
320
|
# +table_name+. Passing a hash containing <tt>:from</tt> and <tt>:to</tt>
|
321
321
|
# as +default_or_changes+ will make this change reversible in the migration.
|
322
322
|
# * <tt>change_column_null(table_name, column_name, null, default = nil)</tt>:
|
323
|
-
# Sets or removes a
|
323
|
+
# Sets or removes a <tt>NOT NULL</tt> constraint on +column_name+. The +null+ flag
|
324
324
|
# indicates whether the value can be +NULL+. See
|
325
325
|
# ActiveRecord::ConnectionAdapters::SchemaStatements#change_column_null for
|
326
326
|
# details.
|
@@ -122,9 +122,9 @@ module ActiveRecord
|
|
122
122
|
# :singleton-method: immutable_strings_by_default=
|
123
123
|
# :call-seq: immutable_strings_by_default=(bool)
|
124
124
|
#
|
125
|
-
# Determines whether columns should infer their type as
|
126
|
-
#
|
127
|
-
#
|
125
|
+
# Determines whether columns should infer their type as +:string+ or
|
126
|
+
# +:immutable_string+. This setting does not affect the behavior of
|
127
|
+
# <tt>attribute :foo, :string</tt>. Defaults to false.
|
128
128
|
|
129
129
|
included do
|
130
130
|
mattr_accessor :primary_key_prefix_type, instance_writer: false
|
@@ -316,7 +316,7 @@ module ActiveRecord
|
|
316
316
|
# self.ignored_columns = [:category]
|
317
317
|
# end
|
318
318
|
#
|
319
|
-
# The schema still contains
|
319
|
+
# The schema still contains "category", but now the model omits it, so any meta-driven code or
|
320
320
|
# schema caching will not attempt to use the column:
|
321
321
|
#
|
322
322
|
# Project.columns_hash["category"] => nil
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
ActiveRecord::
|
4
|
-
|
5
|
-
at_exit do
|
6
|
-
ActiveRecord::Base.connection.rollback_transaction
|
3
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback(:checkout, :after) do
|
4
|
+
begin_transaction(joinable: false)
|
7
5
|
end
|