activerecord 8.0.2 → 8.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +143 -2
- data/README.rdoc +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +9 -1
- data/lib/active_record/associations/collection_association.rb +3 -3
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +1 -1
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/attributes.rb +35 -24
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -6
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +4 -1
- data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +9 -2
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -2
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +8 -6
- data/lib/active_record/database_configurations/hash_config.rb +8 -2
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +1 -1
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/encryptor.rb +27 -25
- data/lib/active_record/enum.rb +13 -12
- data/lib/active_record/errors.rb +3 -3
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/gem_version.rb +1 -1
- data/lib/active_record/migration.rb +5 -5
- data/lib/active_record/query_logs.rb +4 -0
- data/lib/active_record/querying.rb +4 -4
- data/lib/active_record/railtie.rb +2 -2
- data/lib/active_record/railties/databases.rake +8 -16
- data/lib/active_record/relation/calculations.rb +15 -16
- data/lib/active_record/relation/finder_methods.rb +14 -13
- data/lib/active_record/relation/query_methods.rb +4 -4
- data/lib/active_record/relation/spawn_methods.rb +6 -6
- data/lib/active_record/relation/where_clause.rb +8 -2
- data/lib/active_record/relation.rb +14 -4
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +3 -3
- data/lib/active_record/tasks/database_tasks.rb +22 -14
- data/lib/active_record/tasks/postgresql_database_tasks.rb +9 -1
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/transactions.rb +3 -1
- data/lib/active_record.rb +3 -2
- data/lib/arel/crud.rb +2 -0
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/select_manager.rb +6 -2
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -0
- data/lib/arel/visitors/to_sql.rb +2 -0
- metadata +10 -10
data/lib/active_record/core.rb
CHANGED
@@ -277,7 +277,7 @@ module ActiveRecord
|
|
277
277
|
return super if StatementCache.unsupported_value?(id)
|
278
278
|
|
279
279
|
cached_find_by([primary_key], [id]) ||
|
280
|
-
raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", name, primary_key, id))
|
280
|
+
raise(RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id.inspect}", name, primary_key, id))
|
281
281
|
end
|
282
282
|
|
283
283
|
def find_by(*args) # :nodoc:
|
@@ -600,7 +600,7 @@ module ActiveRecord
|
|
600
600
|
#
|
601
601
|
# topic = Topic.new(title: "Budget", author_name: "Jason")
|
602
602
|
# topic.slice(:title, :author_name)
|
603
|
-
# => { "title" => "Budget", "author_name" => "Jason" }
|
603
|
+
# # => { "title" => "Budget", "author_name" => "Jason" }
|
604
604
|
#
|
605
605
|
#--
|
606
606
|
# Implemented by ActiveModel::Access#slice.
|
@@ -614,7 +614,7 @@ module ActiveRecord
|
|
614
614
|
#
|
615
615
|
# topic = Topic.new(title: "Budget", author_name: "Jason")
|
616
616
|
# topic.values_at(:title, :author_name)
|
617
|
-
# => ["Budget", "Jason"]
|
617
|
+
# # => ["Budget", "Jason"]
|
618
618
|
#
|
619
619
|
#--
|
620
620
|
# Implemented by ActiveModel::Access#values_at.
|
@@ -691,12 +691,14 @@ module ActiveRecord
|
|
691
691
|
# Sets the record to strict_loading mode. This will raise an error
|
692
692
|
# if the record tries to lazily load an association.
|
693
693
|
#
|
694
|
+
# NOTE: Strict loading is disabled during validation in order to let the record validate its association.
|
695
|
+
#
|
694
696
|
# user = User.first
|
695
697
|
# user.strict_loading! # => true
|
696
698
|
# user.address.city
|
697
|
-
# => ActiveRecord::StrictLoadingViolationError
|
699
|
+
# # => ActiveRecord::StrictLoadingViolationError
|
698
700
|
# user.comments.to_a
|
699
|
-
# => ActiveRecord::StrictLoadingViolationError
|
701
|
+
# # => ActiveRecord::StrictLoadingViolationError
|
700
702
|
#
|
701
703
|
# ==== Parameters
|
702
704
|
#
|
@@ -716,7 +718,7 @@ module ActiveRecord
|
|
716
718
|
# user.address.city # => "Tatooine"
|
717
719
|
# user.comments.to_a # => [#<Comment:0x00...]
|
718
720
|
# user.comments.first.ratings.to_a
|
719
|
-
# => ActiveRecord::StrictLoadingViolationError
|
721
|
+
# # => ActiveRecord::StrictLoadingViolationError
|
720
722
|
def strict_loading!(value = true, mode: :all)
|
721
723
|
unless [:all, :n_plus_one_only].include?(mode)
|
722
724
|
raise ArgumentError, "The :mode option must be one of [:all, :n_plus_one_only] but #{mode.inspect} was provided."
|
@@ -146,7 +146,7 @@ module ActiveRecord
|
|
146
146
|
#
|
147
147
|
# If the config option is set that will be used. Otherwise Rails will generate
|
148
148
|
# the filename from the database config name.
|
149
|
-
def schema_dump(format =
|
149
|
+
def schema_dump(format = schema_format)
|
150
150
|
if configuration_hash.key?(:schema_dump)
|
151
151
|
if config = configuration_hash[:schema_dump]
|
152
152
|
config
|
@@ -158,6 +158,12 @@ module ActiveRecord
|
|
158
158
|
end
|
159
159
|
end
|
160
160
|
|
161
|
+
def schema_format # :nodoc:
|
162
|
+
format = configuration_hash[:schema_format]&.to_sym || ActiveRecord.schema_format
|
163
|
+
raise "Invalid schema format" unless [ :ruby, :sql ].include? format
|
164
|
+
format
|
165
|
+
end
|
166
|
+
|
161
167
|
def database_tasks? # :nodoc:
|
162
168
|
!replica? && !!configuration_hash.fetch(:database_tasks, true)
|
163
169
|
end
|
@@ -168,7 +174,7 @@ module ActiveRecord
|
|
168
174
|
|
169
175
|
private
|
170
176
|
def schema_file_type(format)
|
171
|
-
case format
|
177
|
+
case format.to_sym
|
172
178
|
when :ruby
|
173
179
|
"schema.rb"
|
174
180
|
when :sql
|
@@ -202,7 +202,7 @@ module ActiveRecord
|
|
202
202
|
# @entry.access_notice_message
|
203
203
|
# @entry.access_notice_message?
|
204
204
|
#
|
205
|
-
#
|
205
|
+
# ==== Options
|
206
206
|
#
|
207
207
|
# The +options+ are passed directly to the +belongs_to+ call, so this is where you declare +dependent+ etc.
|
208
208
|
# The following options can be included to specialize the behavior of the delegated type convenience methods.
|
@@ -16,7 +16,7 @@ module ActiveRecord
|
|
16
16
|
class_methods do
|
17
17
|
# Encrypts the +name+ attribute.
|
18
18
|
#
|
19
|
-
#
|
19
|
+
# ==== Options
|
20
20
|
#
|
21
21
|
# * <tt>:key_provider</tt> - A key provider to provide encryption and decryption keys. Defaults to
|
22
22
|
# +ActiveRecord::Encryption.key_provider+.
|
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme
|
16
16
|
delegate :accessor, :type, to: :cast_type
|
17
17
|
|
18
|
-
#
|
18
|
+
# ==== Options
|
19
19
|
#
|
20
20
|
# * <tt>:scheme</tt> - A +Scheme+ with the encryption properties for this attribute.
|
21
21
|
# * <tt>:cast_type</tt> - A type that will be used to serialize (before encrypting) and deserialize
|
@@ -11,41 +11,43 @@ module ActiveRecord
|
|
11
11
|
# It interacts with a KeyProvider for getting the keys, and delegate to
|
12
12
|
# ActiveRecord::Encryption::Cipher the actual encryption algorithm.
|
13
13
|
class Encryptor
|
14
|
-
# The compressor to use for compressing the payload
|
14
|
+
# The compressor to use for compressing the payload.
|
15
15
|
attr_reader :compressor
|
16
16
|
|
17
|
-
#
|
17
|
+
# ==== Options
|
18
18
|
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
19
|
+
# [+:compress+]
|
20
|
+
# Boolean indicating whether records should be compressed before
|
21
|
+
# encryption. Defaults to +true+.
|
22
|
+
#
|
23
|
+
# [+:compressor+]
|
24
|
+
# The compressor to use. It must respond to +deflate+ and +inflate+.
|
25
|
+
# If not provided, will default to +ActiveRecord::Encryption.config.compressor+,
|
26
|
+
# which itself defaults to +Zlib+.
|
25
27
|
def initialize(compress: true, compressor: nil)
|
26
28
|
@compress = compress
|
27
29
|
@compressor = compressor || ActiveRecord::Encryption.config.compressor
|
28
30
|
end
|
29
31
|
|
30
|
-
# Encrypts +clean_text+ and returns the encrypted result
|
32
|
+
# Encrypts +clean_text+ and returns the encrypted result.
|
31
33
|
#
|
32
34
|
# Internally, it will:
|
33
35
|
#
|
34
|
-
# 1. Create a new ActiveRecord::Encryption::Message
|
35
|
-
# 2. Compress and encrypt +clean_text+ as the message payload
|
36
|
-
# 3. Serialize it with +ActiveRecord::Encryption.message_serializer+
|
37
|
-
# by default)
|
38
|
-
# 4. Encode the result with
|
36
|
+
# 1. Create a new ActiveRecord::Encryption::Message.
|
37
|
+
# 2. Compress and encrypt +clean_text+ as the message payload.
|
38
|
+
# 3. Serialize it with +ActiveRecord::Encryption.message_serializer+
|
39
|
+
# (+ActiveRecord::Encryption::SafeMarshal+ by default).
|
40
|
+
# 4. Encode the result with Base64.
|
39
41
|
#
|
40
|
-
#
|
42
|
+
# ==== Options
|
41
43
|
#
|
42
|
-
# [
|
44
|
+
# [+:key_provider+]
|
43
45
|
# Key provider to use for the encryption operation. It will default to
|
44
46
|
# +ActiveRecord::Encryption.key_provider+ when not provided.
|
45
47
|
#
|
46
|
-
# [
|
48
|
+
# [+:cipher_options+]
|
47
49
|
# Cipher-specific options that will be passed to the Cipher configured in
|
48
|
-
# +ActiveRecord::Encryption.cipher
|
50
|
+
# +ActiveRecord::Encryption.cipher+.
|
49
51
|
def encrypt(clear_text, key_provider: default_key_provider, cipher_options: {})
|
50
52
|
clear_text = force_encoding_if_needed(clear_text) if cipher_options[:deterministic]
|
51
53
|
|
@@ -53,17 +55,17 @@ module ActiveRecord
|
|
53
55
|
serialize_message build_encrypted_message(clear_text, key_provider: key_provider, cipher_options: cipher_options)
|
54
56
|
end
|
55
57
|
|
56
|
-
# Decrypts an +encrypted_text+ and returns the result as clean text
|
58
|
+
# Decrypts an +encrypted_text+ and returns the result as clean text.
|
57
59
|
#
|
58
|
-
#
|
60
|
+
# ==== Options
|
59
61
|
#
|
60
|
-
# [
|
62
|
+
# [+:key_provider+]
|
61
63
|
# Key provider to use for the encryption operation. It will default to
|
62
|
-
# +ActiveRecord::Encryption.key_provider+ when not provided
|
64
|
+
# +ActiveRecord::Encryption.key_provider+ when not provided.
|
63
65
|
#
|
64
|
-
# [
|
66
|
+
# [+:cipher_options+]
|
65
67
|
# Cipher-specific options that will be passed to the Cipher configured in
|
66
|
-
# +ActiveRecord::Encryption.cipher
|
68
|
+
# +ActiveRecord::Encryption.cipher+.
|
67
69
|
def decrypt(encrypted_text, key_provider: default_key_provider, cipher_options: {})
|
68
70
|
message = deserialize_message(encrypted_text)
|
69
71
|
keys = key_provider.decryption_keys(message)
|
@@ -73,7 +75,7 @@ module ActiveRecord
|
|
73
75
|
raise Errors::Decryption
|
74
76
|
end
|
75
77
|
|
76
|
-
# Returns whether the text is encrypted or not
|
78
|
+
# Returns whether the text is encrypted or not.
|
77
79
|
def encrypted?(text)
|
78
80
|
deserialize_message(text)
|
79
81
|
true
|
data/lib/active_record/enum.rb
CHANGED
@@ -119,7 +119,18 @@ module ActiveRecord
|
|
119
119
|
# enum :status, [ :active, :archived ], instance_methods: false
|
120
120
|
# end
|
121
121
|
#
|
122
|
-
#
|
122
|
+
# By default, an +ArgumentError+ will be raised when assigning an invalid value:
|
123
|
+
#
|
124
|
+
# class Conversation < ActiveRecord::Base
|
125
|
+
# enum :status, [ :active, :archived ]
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# conversation = Conversation.new
|
129
|
+
#
|
130
|
+
# conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
|
131
|
+
#
|
132
|
+
# If, instead, you want the enum value to be validated before saving, use the
|
133
|
+
# +:validate+ option:
|
123
134
|
#
|
124
135
|
# class Conversation < ActiveRecord::Base
|
125
136
|
# enum :status, [ :active, :archived ], validate: true
|
@@ -136,7 +147,7 @@ module ActiveRecord
|
|
136
147
|
# conversation.status = :active
|
137
148
|
# conversation.valid? # => true
|
138
149
|
#
|
139
|
-
#
|
150
|
+
# You may also pass additional validation options:
|
140
151
|
#
|
141
152
|
# class Conversation < ActiveRecord::Base
|
142
153
|
# enum :status, [ :active, :archived ], validate: { allow_nil: true }
|
@@ -152,16 +163,6 @@ module ActiveRecord
|
|
152
163
|
#
|
153
164
|
# conversation.status = :active
|
154
165
|
# conversation.valid? # => true
|
155
|
-
#
|
156
|
-
# Otherwise +ArgumentError+ will raise:
|
157
|
-
#
|
158
|
-
# class Conversation < ActiveRecord::Base
|
159
|
-
# enum :status, [ :active, :archived ]
|
160
|
-
# end
|
161
|
-
#
|
162
|
-
# conversation = Conversation.new
|
163
|
-
#
|
164
|
-
# conversation.status = :unknown # 'unknown' is not a valid status (ArgumentError)
|
165
166
|
module Enum
|
166
167
|
def self.extended(base) # :nodoc:
|
167
168
|
base.class_attribute(:defined_enums, instance_writer: false, default: {})
|
data/lib/active_record/errors.rb
CHANGED
@@ -13,7 +13,7 @@ module ActiveRecord
|
|
13
13
|
|
14
14
|
# Raised when the single-table inheritance mechanism fails to locate the subclass
|
15
15
|
# (for example due to improper usage of column that
|
16
|
-
# {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema
|
16
|
+
# {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema.inheritance_column]
|
17
17
|
# points to).
|
18
18
|
class SubclassNotFound < ActiveRecordError
|
19
19
|
end
|
@@ -444,7 +444,7 @@ module ActiveRecord
|
|
444
444
|
UnknownAttributeError = ActiveModel::UnknownAttributeError
|
445
445
|
|
446
446
|
# Raised when an error occurred while doing a mass assignment to an attribute through the
|
447
|
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
|
447
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=] method.
|
448
448
|
# The exception has an +attribute+ property that is the name of the offending attribute.
|
449
449
|
class AttributeAssignmentError < ActiveRecordError
|
450
450
|
attr_reader :exception, :attribute
|
@@ -457,7 +457,7 @@ module ActiveRecord
|
|
457
457
|
end
|
458
458
|
|
459
459
|
# Raised when there are multiple errors while doing a mass assignment through the
|
460
|
-
# {ActiveRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=]
|
460
|
+
# {ActiveRecord::Base#attributes=}[rdoc-ref:ActiveModel::AttributeAssignment#attributes=]
|
461
461
|
# method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
|
462
462
|
# objects, each corresponding to the error while assigning to an attribute.
|
463
463
|
class MultiparameterAssignmentErrors < ActiveRecordError
|
@@ -193,8 +193,25 @@ module ActiveRecord
|
|
193
193
|
|
194
194
|
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
|
195
195
|
joins = targets.map do |target|
|
196
|
-
join = {
|
197
|
-
|
196
|
+
join = {}
|
197
|
+
|
198
|
+
if rhs_key.is_a?(Array)
|
199
|
+
composite_key = ActiveRecord::FixtureSet.composite_identify(target, rhs_key)
|
200
|
+
composite_key.each do |column, value|
|
201
|
+
join[column] = value
|
202
|
+
end
|
203
|
+
else
|
204
|
+
join[rhs_key] = ActiveRecord::FixtureSet.identify(target, column_type)
|
205
|
+
end
|
206
|
+
|
207
|
+
if lhs_key.is_a?(Array)
|
208
|
+
lhs_key.zip(model_metadata.primary_key_name).each do |fkey, pkey|
|
209
|
+
join[fkey] = @row[pkey]
|
210
|
+
end
|
211
|
+
else
|
212
|
+
join[lhs_key] = @row[model_metadata.primary_key_name]
|
213
|
+
end
|
214
|
+
|
198
215
|
association.timestamp_column_names.each do |col|
|
199
216
|
join[col] = @now
|
200
217
|
end
|
@@ -148,11 +148,10 @@ module ActiveRecord
|
|
148
148
|
include ActiveSupport::ActionableError
|
149
149
|
|
150
150
|
action "Run pending migrations" do
|
151
|
-
ActiveRecord::Tasks::DatabaseTasks.
|
151
|
+
ActiveRecord::Tasks::DatabaseTasks.migrate_all
|
152
152
|
|
153
153
|
if ActiveRecord.dump_schema_after_migration
|
154
|
-
|
155
|
-
ActiveRecord::Tasks::DatabaseTasks.dump_schema(connection.pool.db_config)
|
154
|
+
ActiveRecord::Tasks::DatabaseTasks.dump_all
|
156
155
|
end
|
157
156
|
end
|
158
157
|
|
@@ -747,7 +746,7 @@ module ActiveRecord
|
|
747
746
|
private
|
748
747
|
def any_schema_needs_update?
|
749
748
|
!db_configs_in_current_env.all? do |db_config|
|
750
|
-
Tasks::DatabaseTasks.schema_up_to_date?(db_config
|
749
|
+
Tasks::DatabaseTasks.schema_up_to_date?(db_config)
|
751
750
|
end
|
752
751
|
end
|
753
752
|
|
@@ -1529,7 +1528,8 @@ module ActiveRecord
|
|
1529
1528
|
return if down? && !migrated.include?(migration.version.to_i)
|
1530
1529
|
return if up? && migrated.include?(migration.version.to_i)
|
1531
1530
|
|
1532
|
-
|
1531
|
+
message = up? ? "Migrating to" : "Reverting"
|
1532
|
+
Base.logger.info "#{message} #{migration.name} (#{migration.version})" if Base.logger
|
1533
1533
|
|
1534
1534
|
ddl_transaction(migration) do
|
1535
1535
|
migration.migrate(@direction)
|
@@ -28,6 +28,10 @@ module ActiveRecord
|
|
28
28
|
# * +database+
|
29
29
|
# * +source_location+
|
30
30
|
#
|
31
|
+
# WARNING: Calculating the +source_location+ of a query can be slow, so you should consider its impact if using it in a production environment.
|
32
|
+
#
|
33
|
+
# Also see {config.active_record.verbose_query_logs}[https://guides.rubyonrails.org/debugging_rails_applications.html#verbose-query-logs].
|
34
|
+
#
|
31
35
|
# Action Controller adds default tags when loaded:
|
32
36
|
#
|
33
37
|
# * +controller+
|
@@ -46,8 +46,8 @@ module ActiveRecord
|
|
46
46
|
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
|
47
47
|
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
|
48
48
|
#
|
49
|
-
# Note that building your own SQL query string from user input may expose your application to
|
50
|
-
# injection attacks
|
49
|
+
# Note that building your own SQL query string from user input {may expose your application to
|
50
|
+
# injection attacks}[https://guides.rubyonrails.org/security.html#sql-injection].
|
51
51
|
def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
|
52
52
|
result = with_connection do |c|
|
53
53
|
_query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry)
|
@@ -55,7 +55,7 @@ module ActiveRecord
|
|
55
55
|
_load_from_sql(result, &block)
|
56
56
|
end
|
57
57
|
|
58
|
-
# Same as
|
58
|
+
# Same as #find_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise.
|
59
59
|
def async_find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
|
60
60
|
with_connection do |c|
|
61
61
|
_query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry, async: true)
|
@@ -112,7 +112,7 @@ module ActiveRecord
|
|
112
112
|
end
|
113
113
|
end
|
114
114
|
|
115
|
-
# Same as
|
115
|
+
# Same as #count_by_sql but perform the query asynchronously and returns an ActiveRecord::Promise.
|
116
116
|
def async_count_by_sql(sql)
|
117
117
|
with_connection do |c|
|
118
118
|
c.select_value(sanitize_sql(sql), "#{name} Count", async: true).then(&:to_i)
|
@@ -23,8 +23,8 @@ module ActiveRecord
|
|
23
23
|
config.action_dispatch.rescue_responses.merge!(
|
24
24
|
"ActiveRecord::RecordNotFound" => :not_found,
|
25
25
|
"ActiveRecord::StaleObjectError" => :conflict,
|
26
|
-
"ActiveRecord::RecordInvalid" =>
|
27
|
-
"ActiveRecord::RecordNotSaved" =>
|
26
|
+
"ActiveRecord::RecordInvalid" => ActionDispatch::Constants::UNPROCESSABLE_CONTENT,
|
27
|
+
"ActiveRecord::RecordNotSaved" => ActionDispatch::Constants::UNPROCESSABLE_CONTENT
|
28
28
|
)
|
29
29
|
|
30
30
|
config.active_record.use_schema_cache_dump = true
|
@@ -447,18 +447,14 @@ db_namespace = namespace :db do
|
|
447
447
|
namespace :schema do
|
448
448
|
desc "Create a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`)"
|
449
449
|
task dump: :load_config do
|
450
|
-
ActiveRecord::Tasks::DatabaseTasks.
|
451
|
-
db_config = pool.db_config
|
452
|
-
schema_format = ENV.fetch("SCHEMA_FORMAT", ActiveRecord.schema_format).to_sym
|
453
|
-
ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, schema_format)
|
454
|
-
end
|
450
|
+
ActiveRecord::Tasks::DatabaseTasks.dump_all
|
455
451
|
|
456
452
|
db_namespace["schema:dump"].reenable
|
457
453
|
end
|
458
454
|
|
459
455
|
desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on `ENV['SCHEMA_FORMAT']` or `config.active_record.schema_format`) into the database"
|
460
456
|
task load: [:load_config, :check_protected_environments] do
|
461
|
-
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(
|
457
|
+
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(ENV["SCHEMA_FORMAT"], ENV["SCHEMA"])
|
462
458
|
end
|
463
459
|
|
464
460
|
namespace :dump do
|
@@ -467,8 +463,7 @@ db_namespace = namespace :db do
|
|
467
463
|
task name => :load_config do
|
468
464
|
ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool|
|
469
465
|
db_config = pool.db_config
|
470
|
-
|
471
|
-
ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, schema_format)
|
466
|
+
ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
|
472
467
|
end
|
473
468
|
|
474
469
|
db_namespace["schema:dump:#{name}"].reenable
|
@@ -478,12 +473,11 @@ db_namespace = namespace :db do
|
|
478
473
|
|
479
474
|
namespace :load do
|
480
475
|
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
|
481
|
-
desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on
|
476
|
+
desc "Load a database schema file (either db/schema.rb or db/structure.sql, depending on configuration) into the #{name} database"
|
482
477
|
task name => "db:test:purge:#{name}" do
|
483
478
|
ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(name: name) do |pool|
|
484
479
|
db_config = pool.db_config
|
485
|
-
|
486
|
-
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
|
480
|
+
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
|
487
481
|
end
|
488
482
|
end
|
489
483
|
end
|
@@ -527,13 +521,12 @@ db_namespace = namespace :db do
|
|
527
521
|
end
|
528
522
|
|
529
523
|
namespace :test do
|
530
|
-
# desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on
|
524
|
+
# desc "Recreate the test database from an existent schema file (schema.rb or structure.sql, depending on configuration)"
|
531
525
|
task load_schema: %w(db:test:purge) do
|
532
526
|
ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test") do |pool|
|
533
527
|
db_config = pool.db_config
|
534
528
|
ActiveRecord::Schema.verbose = false
|
535
|
-
|
536
|
-
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
|
529
|
+
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
|
537
530
|
end
|
538
531
|
end
|
539
532
|
|
@@ -558,8 +551,7 @@ db_namespace = namespace :db do
|
|
558
551
|
ActiveRecord::Tasks::DatabaseTasks.with_temporary_pool_for_each(env: "test", name: name) do |pool|
|
559
552
|
db_config = pool.db_config
|
560
553
|
ActiveRecord::Schema.verbose = false
|
561
|
-
|
562
|
-
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, schema_format)
|
554
|
+
ActiveRecord::Tasks::DatabaseTasks.load_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
|
563
555
|
end
|
564
556
|
end
|
565
557
|
end
|
@@ -60,37 +60,37 @@ module ActiveRecord
|
|
60
60
|
# Person.distinct.count(:age)
|
61
61
|
# # => counts the number of different age values
|
62
62
|
#
|
63
|
-
# If
|
63
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group],
|
64
64
|
# it returns a Hash whose keys represent the aggregated column,
|
65
65
|
# and the values are the respective amounts:
|
66
66
|
#
|
67
67
|
# Person.group(:city).count
|
68
68
|
# # => { 'Rome' => 5, 'Paris' => 3 }
|
69
69
|
#
|
70
|
-
# If
|
70
|
+
# If +count+ is used with {Relation#group}[rdoc-ref:QueryMethods#group] for multiple columns, it returns a Hash whose
|
71
71
|
# keys are an array containing the individual values of each column and the value
|
72
|
-
# of each key would be the
|
72
|
+
# of each key would be the count.
|
73
73
|
#
|
74
74
|
# Article.group(:status, :category).count
|
75
75
|
# # => {["draft", "business"]=>10, ["draft", "technology"]=>4, ["published", "technology"]=>2}
|
76
76
|
#
|
77
|
-
# If
|
77
|
+
# If +count+ is used with {Relation#select}[rdoc-ref:QueryMethods#select], it will count the selected columns:
|
78
78
|
#
|
79
79
|
# Person.select(:age).count
|
80
80
|
# # => counts the number of different age values
|
81
81
|
#
|
82
|
-
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid
|
82
|
+
# Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid +count+ expressions. The specifics differ
|
83
83
|
# between databases. In invalid cases, an error from the database is thrown.
|
84
84
|
#
|
85
|
-
# When given a block,
|
86
|
-
#
|
87
|
-
# Returns the number of records for which the block returns a truthy value.
|
85
|
+
# When given a block, calls the block with each record in the relation and
|
86
|
+
# returns the number of records for which the block returns a truthy value.
|
88
87
|
#
|
89
88
|
# Person.count { |person| person.age > 21 }
|
90
89
|
# # => counts the number of people older that 21
|
91
90
|
#
|
92
|
-
#
|
93
|
-
#
|
91
|
+
# If the relation hasn't been loaded yet, calling +count+ with a block will
|
92
|
+
# load all records in the relation. If there are a lot of records in the
|
93
|
+
# relation, loading all records could result in performance issues.
|
94
94
|
def count(column_name = nil)
|
95
95
|
if block_given?
|
96
96
|
unless column_name.nil?
|
@@ -159,16 +159,15 @@ module ActiveRecord
|
|
159
159
|
#
|
160
160
|
# Person.sum(:age) # => 4562
|
161
161
|
#
|
162
|
-
# When given a block,
|
163
|
-
#
|
164
|
-
# Returns the sum of +initial_value_or_column+ and the block return
|
165
|
-
# values:
|
162
|
+
# When given a block, calls the block with each record in the relation and
|
163
|
+
# returns the sum of +initial_value_or_column+ plus the block return values:
|
166
164
|
#
|
167
165
|
# Person.sum { |person| person.age } # => 4562
|
168
166
|
# Person.sum(1000) { |person| person.age } # => 5562
|
169
167
|
#
|
170
|
-
#
|
171
|
-
#
|
168
|
+
# If the relation hasn't been loaded yet, calling +sum+ with a block will
|
169
|
+
# load all records in the relation. If there are a lot of records in the
|
170
|
+
# relation, loading all records could result in performance issues.
|
172
171
|
def sum(initial_value_or_column = 0, &block)
|
173
172
|
if block_given?
|
174
173
|
map(&block).sum(initial_value_or_column)
|
@@ -24,22 +24,22 @@ module ActiveRecord
|
|
24
24
|
# TravelRoute.primary_key = [:origin, :destination]
|
25
25
|
#
|
26
26
|
# TravelRoute.find(["Ottawa", "London"])
|
27
|
-
# => #<TravelRoute origin: "Ottawa", destination: "London">
|
27
|
+
# # => #<TravelRoute origin: "Ottawa", destination: "London">
|
28
28
|
#
|
29
29
|
# TravelRoute.find([["Paris", "Montreal"]])
|
30
|
-
# => [#<TravelRoute origin: "Paris", destination: "Montreal">]
|
30
|
+
# # => [#<TravelRoute origin: "Paris", destination: "Montreal">]
|
31
31
|
#
|
32
32
|
# TravelRoute.find(["New York", "Las Vegas"], ["New York", "Portland"])
|
33
|
-
# => [
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
33
|
+
# # => [
|
34
|
+
# # #<TravelRoute origin: "New York", destination: "Las Vegas">,
|
35
|
+
# # #<TravelRoute origin: "New York", destination: "Portland">
|
36
|
+
# # ]
|
37
37
|
#
|
38
38
|
# TravelRoute.find([["Berlin", "London"], ["Barcelona", "Lisbon"]])
|
39
|
-
# => [
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
39
|
+
# # => [
|
40
|
+
# # #<TravelRoute origin: "Berlin", destination: "London">,
|
41
|
+
# # #<TravelRoute origin: "Barcelona", destination: "Lisbon">
|
42
|
+
# # ]
|
43
43
|
#
|
44
44
|
# NOTE: The returned records are in the same order as the ids you provide.
|
45
45
|
# If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where
|
@@ -424,12 +424,13 @@ module ActiveRecord
|
|
424
424
|
error << " with#{conditions}" if conditions
|
425
425
|
raise RecordNotFound.new(error, name, key)
|
426
426
|
elsif Array.wrap(ids).size == 1
|
427
|
-
|
427
|
+
id = Array.wrap(ids)[0]
|
428
|
+
error = "Couldn't find #{name} with '#{key}'=#{id.inspect}#{conditions}"
|
428
429
|
raise RecordNotFound.new(error, name, key, ids)
|
429
430
|
else
|
430
431
|
error = +"Couldn't find all #{name.pluralize} with '#{key}': "
|
431
|
-
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
|
432
|
-
error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids
|
432
|
+
error << "(#{ids.map(&:inspect).join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})."
|
433
|
+
error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.map(&:inspect).join(', ')}." if not_found_ids
|
433
434
|
raise RecordNotFound.new(error, name, key, ids)
|
434
435
|
end
|
435
436
|
end
|
@@ -510,7 +510,7 @@ module ActiveRecord
|
|
510
510
|
# # WITH RECURSIVE post_and_replies AS (
|
511
511
|
# # (SELECT * FROM posts WHERE id = 42)
|
512
512
|
# # UNION ALL
|
513
|
-
# # (SELECT * FROM posts JOIN
|
513
|
+
# # (SELECT * FROM posts JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
|
514
514
|
# # )
|
515
515
|
# # SELECT * FROM posts
|
516
516
|
#
|
@@ -1299,13 +1299,13 @@ module ActiveRecord
|
|
1299
1299
|
#
|
1300
1300
|
# users = User.readonly
|
1301
1301
|
# users.first.save
|
1302
|
-
# => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
1302
|
+
# # => ActiveRecord::ReadOnlyRecord: User is marked as readonly
|
1303
1303
|
#
|
1304
1304
|
# To make a readonly relation writable, pass +false+.
|
1305
1305
|
#
|
1306
1306
|
# users.readonly(false)
|
1307
1307
|
# users.first.save
|
1308
|
-
# => true
|
1308
|
+
# # => true
|
1309
1309
|
def readonly(value = true)
|
1310
1310
|
spawn.readonly!(value)
|
1311
1311
|
end
|
@@ -1320,7 +1320,7 @@ module ActiveRecord
|
|
1320
1320
|
#
|
1321
1321
|
# user = User.strict_loading.first
|
1322
1322
|
# user.comments.to_a
|
1323
|
-
# => ActiveRecord::StrictLoadingViolationError
|
1323
|
+
# # => ActiveRecord::StrictLoadingViolationError
|
1324
1324
|
def strict_loading(value = true)
|
1325
1325
|
spawn.strict_loading!(value)
|
1326
1326
|
end
|