Rails中ActiveStorage与ActionText的Mobility国际化集成问题
背景
Rails 内置的 ActiveStorage 通过以下语法处理多态关联:
has_one_attached :document has_many_attached :pictures has_rich_text :body
但这种多态机制与 Mobility gem 的多语言翻译不兼容,Mobility 的翻译声明方式为:
translates :caption, type: :string translates :description, type: :text
迁移异常问题
在 Rails 7 应用中,ActiveStorage 初始化迁移类声明为 class CreateActiveStorageTables < ActiveRecord::Migration[5.2],正向迁移可正常执行,但回滚时无法删除对应表;重启服务器后相关对象仍能渲染,仅执行清理操作才能彻底删除数据。
当前已实现的临时方案
已通过以下迁移为 active_storage_blobs 表添加支持渲染、查询及控制台更新的字段:
class UpdateActiveStorageTables < ActiveRecord::Migration[7.0] def change add_column :active_storage_blobs, :caption, :string add_column :active_storage_blobs, :description, :text end end
并在控制器中通过以下动作更新 Blob 属性:
def add_image images = params[:individual][:image] if images images.each do |image| @individual.images.attach(image) end end @individual.save new_image = @individual.images.last # 假设无并发图片添加操作 new_image.blob.update(caption: params[:individual][:caption], description: params[:individual][:description]) end
注:此实现特殊,因表单中的 caption 和 description 属性不属于图片数据数组
问题解答
1. 能否在模型层通过 Mobility 声明并集成到现有控制器动作中?
可以实现,具体步骤如下:
步骤1:为 ActiveStorage::Blob 配置 Mobility
创建初始化文件 config/initializers/mobility_active_storage_blob.rb,为 ActiveStorage::Blob 注入 Mobility 翻译逻辑:
ActiveStorage::Blob.class_eval do translates :caption, type: :string translates :description, type: :text end
步骤2:生成 Mobility 翻译表
运行以下命令创建翻译表,用于存储多语言的 caption 和 description:
rails generate mobility:translations active_storage_blobs caption:string description:text rails db:migrate
该命令会生成 active_storage_blob_translations 表,自动处理多语言存储逻辑。
步骤3:调整控制器动作
无需大幅修改现有逻辑,仅需将 Blob 更新代码改为 Mobility 兼容的赋值方式:
def add_image images = params[:individual][:image] if images images.each do |image| @individual.images.attach(image) end end @individual.save new_image = @individual.images.last # 默认使用当前 locale 赋值 new_image.blob.caption = params[:individual][:caption] new_image.blob.description = params[:individual][:description] new_image.blob.save end
若需指定特定语言,可直接调用 locale 后缀方法:
new_image.blob.caption_en = params[:individual][:caption_en] new_image.blob.description_fr = params[:individual][:description_fr]
步骤4:视图层适配
在视图中直接调用 Blob 的翻译属性,Mobility 会自动根据当前 locale 返回对应内容:
<% @individual.images.each do |image| %> <%= image_tag(image) %> <p><%= image.blob.caption %></p> <p><%= image.blob.description %></p> <% end %>
2. 创建支持多态的新类,为 Attachment 关联属性并传入翻译数据
若不想直接修改 ActiveStorage::Blob,可创建独立的多态模型关联翻译数据,具体实现如下:
步骤1:生成迁移文件
创建 attachment_metadatas 表,支持与 ActiveStorage::Attachment 的多态关联:
class CreateAttachmentMetadatas < ActiveRecord::Migration[7.0] def change create_table :attachment_metadatas do |t| t.references :attachment, null: false, foreign_key: { to_table: :active_storage_attachments }, index: true t.timestamps end end end
若使用 Mobility 处理该模型的翻译,运行以下命令生成翻译表:
rails generate mobility:translations attachment_metadatas caption:string description:text rails db:migrate
步骤2:定义模型
创建 AttachmentMetadata 模型,建立与 ActiveStorage::Attachment 的关联:
# app/models/attachment_metadata.rb class AttachmentMetadata < ApplicationRecord belongs_to :attachment, class_name: "ActiveStorage::Attachment" # 添加 Mobility 翻译声明 translates :caption, type: :string translates :description, type: :text end
同时,为 ActiveStorage::Attachment 添加反向关联(通过初始化文件实现):
# config/initializers/active_storage_attachment_extension.rb ActiveStorage::Attachment.class_eval do has_one :metadata, class_name: "AttachmentMetadata", dependent: :destroy # 便捷方法:创建/更新元数据 def metadata_attributes=(attrs) metadata ||= AttachmentMetadata.new(attachment: self) metadata.update(attrs) end end
步骤3:调整控制器动作
在控制器中关联新模型的元数据:
def add_image images = params[:individual][:image] if images images.each do |image| attachment = @individual.images.attach(image) # 传入翻译数据到元数据模型 attachment.last.metadata_attributes = { caption: params[:individual][:caption], description: params[:individual][:description] } end end @individual.save end
步骤4:视图层调用
在视图中通过关联的元数据对象获取翻译内容:
<% @individual.images.each do |image| %> <%= image_tag(image) %> <p><%= image.attachment.metadata.caption %></p> <p><%= image.attachment.metadata.description %></p> <% end %>
内容的提问来源于stack exchange,提问作者Jerome

