Rails Action Text 全面指南:富文本编辑、附件管理与安全渲染 Rails Action Text 全面指南富文本编辑、附件管理与安全渲染【免费下载链接】railsRuby on Rails项目地址: https://gitcode.com/GitHub_Trending/rai/railsAction Text 是 Ruby on Rails 内置的富文本处理框架它把所见即所得WYSIWYG编辑器 Trix、ActionText::RichText数据模型与 Active Storage 附件系统整合在一起让你可以用一个has_rich_text声明为任意 Active Record 模型添加格式化正文粗体、斜体、链接、图片、引用等能力并在保存、渲染、安全消毒与附件展示全链路上获得开箱即用的实现。读完本指南你将掌握 Action Text 的安装配置、富文本创建与渲染、Trix 编辑器样式定制以及 Active Storage 直传与 Signed GlobalID 两种附件处理方案的完整实战方法。本指南以仓库文档 action_text_overview.md 为核心骨架并结合本仓库中 actiontext 组件的源码实现进行纵深佐证。什么是 Action TextAction Text 用于便捷地创建、存储与展示富文本内容。所谓富文本是指带有格式元素如粗体、斜体、颜色、超链接的文本相比纯文本拥有更强的视觉表现与结构化信息。它允许我们把富文本内容创建出来、存入数据表再把它挂到任意模型上。Action Text 内置了一个名为 Trix 的 WYSIWYG 编辑器用于在 Web 应用中给用户提供友好、易用的富文本创建与编辑界面。Trix 负责从文本格式化、添加链接或引用到嵌入图片等一系列丰富的编辑能力。由 Trix 编辑器产出的富文本会保存在独立的RichText模型中该模型可与应用中的任意 Active Record 模型建立关联。与此同时正文中嵌入的图片或其他附件会自动通过 Active StorageAction Text 将其作为依赖引入存储并与这条RichText记录关联。渲染时Action Text 会先对内容做安全消毒sanitizing使其能够安全地直接嵌入页面 HTML——这正是富文本内容可直接输出的关键前提。为什么是 Trix而不是contenteditable大多数 WYSIWYG 编辑器都只是对 HTML 的contenteditable与execCommandAPI 的封装。这两个 API 最初由微软为 Internet Explorer 5.5 的网页实时编辑设计之后被其他浏览器逆向并复制。它们从未被完整地规范与文档化而 WYSIWYG HTML 编辑器的范围又极其庞大因此每个浏览器的实现都各有各的 bug 与怪癖JavaScript 开发者常常不得不手工处理这些不一致。Trix 规避这些不一致的方式是把contenteditable当作一个 I/O 设备——当输入进入编辑器时Trix 将其转换为对自己内部文档模型的一次编辑操作然后再把该文档重新渲染回编辑器。这让 Trix 能够完全掌控每一次按键之后发生的一切从而彻底绕开execCommand及其带来的一系列浏览器差异问题。安装与配置 Action Text运行安装命令要安装 Action Text 并开始使用富文本在应用根目录执行$ bin/rails action_text:install以当前仓库中的安装生成器 install_generator.rb 为参考该命令会完成以下几件事安装 JS 依赖并接入打包器安装trix与rails/actiontext两个 JavaScript 包并自动把它们import进application.js若项目使用 importmap则会在config/importmap.rb中追加pin声明。仓库中生成器还支持--editor选项默认trixAction Text 已做成可插拔的编辑器体系见 engine.rb 中config.action_text.editors与config.action_text.editor配置。添加image_processinggem用于对嵌入图片及其他附件执行 Active Storage 的分析与变换。更多细节参见 Active Storage Overview。添加迁移创建存储富文本与附件的表——action_text_rich_texts、active_storage_blobs、active_storage_attachments、active_storage_variant_records。创建actiontext.css包含全部 Trix 样式及 Action Text 所需的覆盖样式。添加默认视图 partial生成渲染 Action Text 内容与 Active Storage 附件即 blob的默认 partial_content.html与_blob.html。之后执行数据库迁移新的action_text_*与active_storage_*表就会进入你的应用$ bin/rails db:migrateaction_text_rich_texts表与多态关联当 Action Text 安装创建action_text_rich_texts表时它使用了多态关联polymorphic association以便多个模型都能添加富文本属性。表结构中的record_type与record_id两列分别存存储拥有富文本的模型的类名ClassName与记录 ID。借助多态关联一个模型可以通过单条关联同时属于多个其他模型。可参见仓库中实际的迁移文件 20180528164100_create_action_text_tables.rbcreate_table :action_text_rich_texts, id: primary_key_type do |t| t.string :name, null: false t.text :body, size: :long t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type t.timestamps t.index [ :record_type, :record_id, :name ], name: index_action_text_rich_texts_uniqueness, unique: true end除多态列外name列记录该富文本所属的 has_rich_text 属性名body列以long文本保存 Trix 序列化后的正文而(record_type, record_id, name)上的唯一索引保证每个模型实例的每个富文本属性只有一条记录。需要指出的是仓库迁移中的主键/外键类型并非写死primary_and_foreign_key_types会读取Rails.configuration.generators下 ORM 的primary_key_type配置默认主键primary_key、外键bigint若应用整体使用 UUID 主键该配置会一并生效。使用 UUID 主键时的注意事项如果包含 Action Text 内容的模型使用 UUID 作为标识符那么所有使用 Action Text 属性的模型都必须统一使用 UUID 主键。同时由于多态外键类型跟随上述全局配置推导生成的迁移可能仍按 bigint 处理record引用为保证一致通常仍需手动修改 Action Text 生成的迁移把 references 行明确为type: :uuidt.references :record, null: false, polymorphic: true, index: false, type: :uuid创建富文本内容本节介绍为模型添加富文本所需的配置步骤。核心机制RichText记录与has_rich_textRichText记录把 Trix 编辑器产出的内容保存在一个经过序列化的body属性中同时持有所有通过 Active Storage 存储的嵌入文件的引用。这条记录会与想要富文本内容的 Active Record 模型关联起来关联方式就是在该模型上调用has_rich_text类方法# app/models/article.rb class Article ApplicationRecord has_rich_text :content end注意不需要在 Article 表中添加content列。has_rich_text会把content关联到已创建的action_text_rich_texts表并回链到你的模型。属性名也可以自定义为content以外的任意名字。从仓库实现 attribute.rb 看has_rich_text会在模型上动态生成一组方法content——懒加载并返回必要时构建对应的RichText记录例如article.content.to_scontent?——判断是否存在非空的富文本正文rich_text_content.present?content——接收来自 Trix 的 HTML 字符串写入正文。其底层通过一条多态has_one关联实现has_one :rich_text_content, as: :record, inverse_of: :record, autosave: true, dependent: :destroy并绑定where(name: name)过滤使同一模型可持有多个不同名字的富文本字段。除了文档中常规用法该方法签名还支持默认值取自源码 attribute.rbencrypted: false——置为true时改用ActionText::EncryptedRichText非确定性加密依赖 Active Record 加密能力strict_loading: strict_loading_by_default——是否强制 strict loadingstore_if_blank: true——置为false时若写入空白值则不再为其创建RichText记录而是标记销毁已有空记录。在表单中使用rich_textarea为模型加上has_rich_text之后就可以在视图中让该字段使用富文本编辑器Trix。做法是把表单字段声明为rich_textarea%# app/views/articles/_form.html.erb % % form_with model: article do |form| % div classfield % form.label :content % % form.rich_textarea :content % /div % end %这会显示一个 Trix 编辑器用于创建与更新富文本。rich_textarea渲染出的是一对元素一个真正的trix-editor可编辑区加上一个隐藏的input——Trix 在用户编辑时会把 HTML 写入该隐藏域从而随表单一起正常提交。其实现位于 tag_helper.rbrich_textarea_tag/rich_textarea默认会把编辑器容器带上classtrix-content保证默认样式生效并自动注入data-direct-upload-url默认rails_direct_uploads_url与data-blob-url-template默认rails_service_blob_url(:signed_id, :filename)两个 data 属性用于编辑器内的图片直传与预览传入块时还可设置默认编辑内容。编辑器样式的更新方式稍后会在「移除或添加 Trix 样式」一节详述。控制器参数白名单最后为了保证能接收来自编辑器的更新需要在对应控制器中把该属性加入允许参数class ArticlesController ApplicationController def create article Article.create! params.expect(article: [:title, :content]) redirect_to article end end重命名模型类时的数据同步一旦有需要重命名使用has_rich_text的类例如把Article改名也必须同步更新action_text_rich_texts表中对应行的多态类型列record_type。由于 Action Text 依赖多态关联而多态关联会把类名存进数据库保持库中数据与 Ruby 代码中的类名一致至关重要否则既存的富文本将无法正确解析回模型。这一点在仓库源码 attribute.rb 的注释中也被明确提醒。渲染富文本内容ActionText::RichText实例可以直接嵌入页面因为其内容在保存/输出阶段已做过安全消毒% article.content %这里实际触发的是ActionText::RichText#to_s它把富文本安全地转换为 HTML 字符串经由 content.rb 的to_s→to_rendered_html_with_layout链路最终套上默认布局 partial 输出。在 rich_text.rb 中可以看到RichText通过serialize :body, coder: ActionText::Content把body序列化为ActionText::Content对象并委托to_s等行为给它Content内部用 Nokogiri 解析 HTML fragment 并做标准化处理。与之相对ActionText::RichText#to_plain_text返回的是去掉标签但保留 HTML 实体编码的纯文本该字符串不是 HTML safe 的未经额外消毒不应直接在浏览器中渲染message Message.create!(content: h1Funny times!/h1) message.content.to_s # h1Funny times!/h1 message.content.to_plain_text # Funny times!同文件还提供了to_markdown(attachment_links: false)可把正文转换为 Markdown在需要编辑态预览时还可用to_editor_html旧名to_trix_html已弃用获得在编辑器中可回填的 HTML。安全消毒消毒发生在保存与渲染过程中。仓库中 engine.rb 通过config.action_text.sanitizer_vendor允许应用替换消毒器实现默认使用 Action View 的 safe-list sanitizerActionText::ContentHelper.sanitizer据此剥离onclick、script之类的危险片段这正是to_s结果可以放心直接输出的原因。注意如果content字段中存在附件资源而本机尚未安装 Active Storage 所需的第三方软件依赖附件可能无法正常显示。定制富文本编辑器Trix当需要按自己的风格要求调整编辑器的呈现时可以按下面的方式定制。移除或添加 Trix 样式默认情况下Action Text 会把富文本内容渲染进一个带.trix-content类的元素中这一行为由app/views/layouts/action_text/contents/_content.html.erb决定仓库内置的默认版本见 layouts/action_text/contents/_content.html.erb仅一行div classtrix-content% yield %/div带有该类的元素再由 trix 样式表进行样式化。如果你想调整任何 trix 样式可在app/assets/stylesheets/actiontext.css中添加自定义样式——这个文件由安装器生成同时包含 Trix 的整套样式与 Action Text 所需的覆盖override。定制内容容器想要定制富文本内容外层包裹的 HTML 容器元素编辑安装器生成的app/views/layouts/action_text/contents/_content.html.erb布局文件%# app/views/layouts/action_text/contents/_content.html.erb % div classtrix-content % yield % /div定制嵌入图片与附件的 HTML要定制嵌入图片及其他附件即 blob渲染出的 HTML编辑安装器生成的app/views/active_storage/blobs/_blob.html.erb模板%# app/views/active_storage/blobs/_blob.html.erb % figure classattachment attachment--% blob.representable? ? preview : file % attachment--% blob.filename.extension % % if blob.representable? % % image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) % % end % figcaption classattachment__caption % if caption blob.try(:caption) % % caption % % else % span classattachment__name% blob.filename %/span span classattachment__size% number_to_human_size blob.byte_size %/span % end % /figcaption /figure仓库中该默认模板位于 actiontext/app/views/active_storage/blobs/_blob.html.erb。它演示了几个关键点可通过blob.representable?区分图片类可预览 blob与普通文件 blob从而为figure施加不同的attachment--preview/attachment--file及按扩展名命名的 CSS 类可预览的 blob 用image_tag blob.representation(...)生成自适应缩略图图库场景in_gallery为真下缩略上限更小800×600图注部分优先显示 blob 的caption否则展示文件名与人类可读的文件大小。附件处理目前 Action Text 支持两类附件通过 Active Storage 上传的附件以及通过 Signed GlobalID 关联的附件。通过 Active Storage 上传附件在富文本编辑器中上传图片时动作由 Action Text 发起底层则使用 Active Storage。不过 Active Storage 有若干第三方依赖 并不由 Rails 提供要使用内置的预览preview能力需要安装这些库。这些库并非全部必需具体取决于你预期在编辑器中接收的上传类型。用户在使用 Action Text 与 Active Storage 时最常遇到的一个问题是图片在编辑器中无法正确渲染。这通常是因为系统没有安装libvips依赖。附件直传的 JavaScript 事件Action Text 在整个文件附件生命周期内都会派发 Active Storage 的 Direct Upload 事件。除常规的event.detail属性之外Action Text 额外派发的事件还会携带event.detail.attachment属性对应本次文件插入所创建的 Trix attachment。事件名事件目标事件数据event.detail描述direct-upload:initializetrix-editor{id, file, attachment}表单提交后对每个文件派发。direct-upload:starttrix-editor{id, file, attachment}一次直传开始。direct-upload:before-blob-requesttrix-editor{id, file, xhr, attachment}在向应用请求直传元数据之前。direct-upload:before-storage-requesttrix-editor{id, file, xhr, attachment}在请求存储文件之前。direct-upload:progresstrix-editor{id, file, progress, attachment}文件存储请求进行中。direct-upload:errortrix-editor{id, file, error, attachment}发生错误。若不取消该事件将弹出alert提示。direct-upload:endtrix-editor{id, file, attachment}一次直传结束。经 Action Text 通过 Active Storage 直传的文件有可能最终并未被嵌入任何富文本内容。建议定期清理这些无主上传purging unattached uploads。相关做法同样见 Active Storage Overview。通过 Signed GlobalID 关联附件除上传到 Active Storage 的附件之外Action Text 还可以嵌入任何能通过 Signed GlobalID 解析的对象。Global ID 是应用级的统一 URI用于唯一标识一个模型实例形如gid://YourApp/Some::Model/id。当你需要用一个标识符去引用不同类型的对象时它非常有用。使用这种方式时Action Text 要求附件具有签名全局 IDsgid。默认情况下Rails 应用中的全部 Active Record 模型都混入了GlobalID::Identificationconcern因此它们都可以被 sgid 解析从而天然兼容ActionText::Attachable。Action Text 会在保存时记录你所插入的 HTML 引用以便之后用最新的内容重新渲染——也就是说你可以引用某个模型并在记录变化后始终展示其当前内容。渲染时Action Text 会先从 global ID 加载出模型再用默认 partial 路径渲染它。一个 Action Text Attachment 看起来是这样的action-text-attachment sgidBAh7CEkiCG…/action-text-attachmentAction Text 渲染内嵌的action-text-attachment元素时会先解析其sgid属性得到对象实例再把实例交给渲染 helper渲染出的 HTML 作为action-text-attachment元素的后代嵌入。要让对象能作为 Attachment 渲染需要include ActionText::Attachable模块该模块通过GlobalID::Identification实现了#to_sgid(**options)class Person ApplicationRecord include ActionText::Attachable end person Person.create! name: Javan html %Q(action-text-attachment sgid#{person.attachable_sgid}/action-text-attachment) content ActionText::Content.new(html) content.attachables # [person]从仓库实现 attachable.rb 看attachable_sgid会生成一个限定用途purpose 为attachable且不过期的 sgidto_sgid(expires_in: nil, for: LOCATOR_NAME)LOCATOR_NAME attachable。也就是说这个签名 ID 只允许被 Action Text 的附件定位器使用无法被用于其他业务目的是一种安全上的隔离。同时ActionText::Content#attachables在解析时会依次尝试 sgid 解析、ContentAttachment、RemoteImage三种来源全部失败则返回一个MissingAttachable占位对象为记录已删除的场景兜底。渲染一个 Action Text Attachmentaction-text-attachment的默认渲染方式是默认路径 partial。下面以 User 模型为例# app/models/user.rb class User ApplicationRecord has_one_attached :avatar end user User.find(1) user.to_global_id.to_s # gid://MyRailsApp/User/1 user.to_signed_global_id.to_s # BAh7CEkiCG…我们可以把GlobalID::Identification混入任何带.find(id)类方法的模型Active Record 模型默认自带该能力。上述代码得到唯一标识该模型实例的 ID。接着看一段嵌入了引用 User 实例 sgid 的action-text-attachment的富文本pHello, action-text-attachment sgidBAh7CEkiCG…/action-text-attachment./pAction Text 用BAh7CEkiCG…解析出 User 实例然后在渲染内容时按默认 partial 路径渲染它。此处的默认 partial 就是users/user%# app/views/users/_user.html.erb % span% image_tag user.avatar % % user.name %/span于是 Action Text 渲染出的最终 HTML 大致如下pHello, action-text-attachment sgidBAh7CEkiCG…spanimg src... Jane Doe/span/action-text-attachment./p为 action-text-attachment 渲染不同的 partial如果想为某个可附件对象渲染不同的 partial可以定义to_attachable_partial_path实例方法其默认值是to_partial_path见 attachable.rbclass User ApplicationRecord def to_attachable_partial_path users/attachable end end然后声明该 partialUser 实例将作为user局部变量可用%# app/views/users/_attachable.html.erb % span% image_tag user.avatar % % user.name %/span为无法解析或缺失的 action-text-attachment 渲染 partial如果 Action Text 无法解析出 User 实例例如记录已被删除默认会渲染一个回退 partial。仓库中默认回退视图为 actiontext/app/views/action_text/attachables/_missing_attachable.html.erb对应默认类方法to_missing_attachable_partial_path见 attachable.rb。想渲染不同的缺失附件 partial定义类级方法to_missing_attachable_partial_pathclass User ApplicationRecord def self.to_missing_attachable_partial_path users/missing_attachable end end然后声明该 partial%# app/views/users/missing_attachable.html.erb % spanDeleted user/span通过 API 使用 Attachable如果你的架构并不遵循传统的 Rails 服务端渲染模式而是一个后端 API例如返回 JSON那么你需要一个独立的文件上传端点。该端点负责创建一个ActiveStorage::Blob并返回它的attachable_sgid{ attachable_sgid: BAh7CEkiCG… }之后在前端代码中把attachable_sgid放进action-text-attachment标签即可把它插入富文本内容action-text-attachment sgidBAh7CEkiCG…/action-text-attachment其他建议避免 N1 查询如果你希望预加载关联的ActionText::RichText模型假设富文本字段名为content可使用has_rich_text自动生成的具名 scope仓库实现见 attribute.rbwith_all_rich_text见同文件rich_text_association_names相关方法Article.all.with_rich_text_content # 仅预加载 body不含附件。 Article.all.with_rich_text_content_and_embeds # 同时预加载 body 与附件含附件需连表 includes embeds_attachments: :blob。若模型持有多个富文本字段还可以用Article.all.with_all_rich_text一次性预加载所有rich_text_*关联。这类预加载能显著降低渲染列表页时的查询数量。小结Action Text 把富文本编辑Trix、结构化存储action_text_rich_texts多态表与文件托管Active Storage三件事编排为开箱即用的一条龙方案模型侧一条has_rich_text即完成接线表单侧一个rich_textarea即获得完整 WYSIWYG 输入输出侧通过消毒后的to_s即可安全渲染对图片等附件Active Storage 直传 事件钩子承担存储Signed GlobalID ActionText::Attachable则打通在正文中引用任意模型的高级玩法。无论是追求快速落地的常规博客/内容场景还是需要深度定制编辑器样式、自定义附件 partial、接入纯 API 前端的架构都可以顺着上文各节的代码路径在仓库源码中继续深入钻研入口可从 actiontext 的lib/action_text、app/models/action_text、app/helpers/action_text与app/views各目录展开。【免费下载链接】railsRuby on Rails项目地址: https://gitcode.com/GitHub_Trending/rai/rails创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考