我正在尝试将我的 Rails 应用程序从使用 Paperclip 转换为用于文件附件的 Active Storage。我正在遵循概述如何通过迁移执行此操作的指南/教程。但是,当我运行迁移时,出现以下错误:
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:
PG::SyntaxError: ERROR: syntax error at or near "active_storage_blob_statement"
LINE 1: active_storage_blob_statement
^
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:61:in `block (4 levels) in up'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:54:in `each'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:54:in `block (3 levels) in up'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:53:in `each'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:53:in `block (2 levels) in up'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:33:in `each'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:33:in `block in up'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:32:in `up'
我不确定是什么原因导致此错误或如何修复它。这是我迁移的相关代码:
class ConvertToActiveStorage < ActiveRecord::Migration[6.1]
require 'open-uri'
def up
# postgres
get_blob_id = 'LASTVAL()'
# mariadb
# get_blob_id = 'LAST_INSERT_ID()'
# sqlite
# get_blob_id = 'LAST_INSERT_ROWID()'
# Prepare two insert statements for the new ActiveStorage tables
active_storage_blob_statement = ActiveRecord::Base.connection.raw_connection.prepare('active_storage_blob_statement', <<-SQL)
INSERT INTO active_storage_blobs (
key, filename, content_type, metadata, byte_size, checksum, created_at
) VALUES ($1, $2, $3, '{}', $4, $5, $6)
SQL
active_storage_attachment_statement = ActiveRecord::Base.connection.raw_connection.prepare('active_storage_attachment_statement', <<-SQL)
INSERT INTO active_storage_attachments (
name, record_type, record_id, blob_id, created_at
) VALUES ($1, $2, $3, #{get_blob_id}, $4)
SQL
# Eager load the application so that all Models are available
Rails.application.eager_load!
# Get a list of all the models in the application
models = ActiveRecord::Base.descendants.reject(&:abstract_class?)
transaction do
models.each do |model|
# If the model has a column or columns named *_file_name,
# We are assuming this is a column added by Paperclip.
# Store the name of the attachment(s) found (e.g. "avatar") in an array named attachments
attachments = model.column_names.map do |c|
if c =~ /(.+)_file_name$/
$1
end
end.compact
# If no Paperclip columns were found in this model, go to the next model
if attachments.blank?
puts ' No Paperclip attachment columns found for [' + model.to_s + '].'
puts ''
next
end
puts ' Paperclip attachment columns found for [' + model.to_s + ']: ' + attachments.to_s
# Loop through the records of the model, and then through each attachment definition within the model
model.find_each.each do |instance|
attachments.each do |attachment|
# If the model record doesn't have an uploaded attachment, skip to the next record
if instance.send(attachment).path.blank?
next
end
# Otherwise, we will convert the Paperclip data to ActiveStorage records
ActiveRecord::Base.connection.execute(
'active_storage_blob_statement', [
key(instance, attachment),
instance.send("#{attachment}_file_name"),
instance.send("#{attachment}_content_type"),
instance.send("#{attachment}_file_size"),
checksum(instance.send(attachment)),
instance.updated_at.iso8601
])
ActiveRecord::Base.connection.execute(
'active_storage_attachment_statement', [
attachment,
model.name,
instance.id,
instance.updated_at.iso8601,
])
end
end
end
end
ActiveRecord::Base.connection.execute('DEALLOCATE PREPARE active_storage_attachment_statement')
ActiveRecord::Base.connection.execute('DEALLOCATE PREPARE active_storage_blob_statement')
end
def down
raise ActiveRecord::IrreversibleMigration
end
private
def key(instance, attachment)
# SecureRandom.uuid
# Alternatively:
filename = instance.send("#{attachment}_file_name")
klass = instance.class.table_name
id = instance.id
id_partition = ("%09d".freeze % id).scan(/\d{3}/).join("/".freeze)
"#{klass}/#{attachment.pluralize}/#{id_partition}/original/#{filename}"
end
def checksum(attachment)
# local files stored on disk:
url = attachment.path
Digest::MD5.base64digest(File.read(url))
# remote files stored on another person's computer:
# url = attachment.url
# Digest::MD5.base64digest(Net::HTTP.get(URI(url)))
end
end
我试过使用 exec_prepared 而不是 execute
ActiveRecord::Base.connection.exec_prepared(
'active_storage_blob_statement', [
key(instance, attachment),
instance.send("#{attachment}_file_name"),
instance.send("#{attachment}_content_type"),
instance.send("#{attachment}_file_size"),
checksum(instance.send(attachment)),
instance.updated_at.iso8601
])
ActiveRecord::Base.connection.exec_prepared(
'active_storage_attachment_statement', [
attachment,
model.name,
instance.id,
instance.updated_at.iso8601,
])
但这给了我以下错误:
Did you mean? exec_update
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:61:in `block (4 levels) in up'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:54:in `each'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:54:in `block (3 levels) in up'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:53:in `each'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:53:in `block (2 levels) in up'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:33:in `each'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:33:in `block in up'
/myapp/db/migrate/20230313230800_convert_to_active_storage.rb:32:in `up'
Caused by:
NoMethodError: undefined method `exec_prepared'
任何人都可以帮助我了解导致此错误的原因以及解决方法吗?提前致谢。
您看到的错误是由于不正确地使用 raw_connection 对象 IMO 中的 prepare 方法引起的。在您提供的迁移代码中,在名为
active_storage_blob_statement
和 active_storage_attachment_statement
的 raw_connection 对象上调用了 prepare 方法,但它没有在 SQL 语句中正确执行。
要修复错误,您需要通过替换以下行来修改迁移代码:
active_storage_blob_statement = ActiveRecord::Base.connection.raw_connection.prepare('active_storage_blob_statement', <<-SQL)
INSERT INTO active_storage_blobs (
key, filename, content_type, metadata, byte_size, checksum, created_at
) VALUES ($1, $2, $3, '{}', $4, $5, $6)
SQL
active_storage_attachment_statement = ActiveRecord::Base.connection.raw_connection.prepare('active_storage_attachment_statement', <<-SQL)
INSERT INTO active_storage_attachments (
name, record_type, record_id, blob_id, created_at
) VALUES ($1, $2, $3, #{get_blob_id}, $4)
SQL
使用以下代码:
active_storage_blob_statement = <<-SQL
INSERT INTO active_storage_blobs (
key, filename, content_type, metadata, byte_size, checksum, created_at
) VALUES ($1, $2, $3, '{}', $4, $5, $6)
SQL
ActiveRecord::Base.connection.execute(
ActiveRecord::Base.send(:sanitize_sql_array, [active_storage_blob_statement]),
'active_storage_blob_statement'
)
active_storage_attachment_statement = <<-SQL
INSERT INTO active_storage_attachments (
name, record_type, record_id, blob_id, created_at
) VALUES ($1, $2, $3, #{get_blob_id}, $4)
SQL
ActiveRecord::Base.connection.execute(
ActiveRecord::Base.send(:sanitize_sql_array, [active_storage_attachment_statement]),
'active_storage_attachment_statement'
)
在此代码中,prepare 方法被替换为包含 SQL 语句的字符串。然后,使用 SQL 语句和准备好的语句的名称在
ActiveRecord::Base.connection
对象上调用 execute 方法。 sanitize_sql_array
方法用于清理 SQL 语句并保护它免受 SQL 注入攻击。
通过进行这些更改,您应该能够在没有任何错误的情况下运行迁移。