Phlex 为 Rails 邮件服务:无需 ERB 的 Action Mailer
使用 Phlex 组件和布局渲染 Action Mailer 邮件:简洁、可组合、纯 Ruby 实现
发布日期:2025年2月27日
更新日期:2025年3月2日
整天写 Ruby 很有趣,但要在 HTML 中嵌入 Ruby 就不那么令人愉快了。在过去几个月里,我发现频繁在 Ruby 和 ERB 视图及部分代码之间切换,极大地打断了我的工作流,甚至让人越来越感到厌烦。
这时我发现了 Phlex(了解更多),这是一款允许你在纯 Ruby 环境下编写 HTML(组件、视图和布局)的 Ruby 库。我已经在多个 Rails 应用中完全用 Phlex 取代了 ERB,因此我想尝试将其应用于 Action Mailer。再见,ERB!
为什么这个方法会如此吸引人呢?除了单纯为了尝试而尝试之外,还有以下几个优势:
- 告别 ERB:无论是邮件还是普通页面,都可以全程使用 Phlex。
- 集中管理 HTML 和文本版邮件内容:在一个地方同时管理邮件的 HTML 和纯文本版本。
- 使用 Phlex 组件构建布局和视图:让代码更加模块化和易于维护。
对于我个人而言,在 Rails 应用中彻底摆脱 ERB 是一件值得庆祝的事情。毕竟,如果某个工具不能带来“喜悦”(参考 Marie Kondo 的整理法则),那我们就应该果断舍弃它。
完整代码可以在我的 GitHub 仓库中找到,希望你也能像我一样觉得这个想法充满吸引力!
项目源码链接: visini/rails-phlex-action-mailer
接下来,我们将从一个基础的 Rails 应用入手,逐步展示如何将所有东西迁移到 Phlex。
ERB 方式的传统方法
UserMailer
类是 Action Mailer 的入口点,负责处理 Rails 应用中的邮件发送功能。下面是一个典型的实现示例:
class UserMailer < ApplicationMailer
def welcome
@user = params[:user]
mail(to: @user.email, subject: "欢迎")
end
end
我们还需要为该邮件提供对应的视图文件或“邮件模板”。由于并非所有邮件客户端都支持 HTML,我们需要分别提供 HTML 和纯文本版本。通过 ERB 模板,我们可以嵌入 Ruby 代码以访问实例变量或调用辅助方法。
HTML 版本模板:
<div>我们很高兴您加入我们!感谢您的注册。</div>
<%= link_to '点击这里登录', 'https://example.com' %>
纯文本版本模板:
我们很高兴您加入我们!感谢您的注册。
点击这里登录:https://example.com
此外,我们还需要定义一些基本的布局。布局中可以添加诸如电子邮件顶部的 logo、底部的链接、联系信息等内容。通过这种方式,修改布局变得非常方便——只需在一处进行更改即可。
HTML 布局模板:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<div><b>应用名称</b></div>
<%= yield %>
<div>此致</div>
<div>公司名称</div>
</body>
</html>
纯文本布局模板:
应用名称
<%= yield %>
此致
公司名称
上述模板结合它们各自的布局后,生成的邮件内容如下:
HTML 版本:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<div><b>应用名称</b></div>
<div>我们很高兴您加入我们!感谢您的注册。</div>
<a href="https://example.com">点击这里登录</a>
<div>此致</div>
<div>公司名称</div>
</body>
</html>
纯文本版本:
应用名称
我们很高兴您加入我们!感谢您的注册。
点击这里登录:https://example.com
此致
公司名称
可以看到,最终生成的邮件内容包含了布局的内容包裹着主体内容。
为了测试这些模板的正确性,我们可以编写单元测试,并通过 fixture 文件来验证 HTML 和纯文本版本的邮件内容是否符合预期。
使用 Phlex 构建邮件模板
首先,按照 Phlex 安装指南 在 Rails 应用中安装 Phlex。接下来,我们可以为邮件模板创建 Phlex 视图。以下是 HTML 和纯文本两个版本的实现。
HTML 版本:
class Views::Mailers::Users::Welcome::Html < Views::Base
def initialize(user)
@user = user
end
def view_template
div do
plain "我们很高兴您加入我们!感谢您的注册。"
end
a href: "https://example.com" do
plain "点击这里登录"
end
end
end
纯文本版本:
class Views::Mailers::Users::Welcome::Text < Views::Base
def initialize(user)
@user = user
end
def view_template
plain "我们很高兴您加入我们!感谢您的注册。"
plain "\n\n"
plain "点击这里登录:https://example.com"
end
end
接着,我们在原来的 ERB 模板中渲染这些 Phlex 视图:
HTML 版本:
<%= render Views::Mailers::Users::Welcome::Html.new(user: @user) %>
纯文本版本:
<%= render Views::Mailers::Users::Welcome::Text.new(user: @user) %>
此时,虽然我们已经通过 Phlex 渲染了视图,但仍然需要保留 ERB 文件作为“入口点”。这看起来有些多余,因为文件数量从原本的两份增加到了四份,增加了复杂性。不过别急,让我们继续改进。
为邮件模板设置 Phlex 布局
类似于 ERB 布局,我们也可以为邮件模板创建 Phlex 布局。首先,我们需要告诉 Action Mailer 不要使用任何默认布局,因为我们将在 Phlex 中手动组装模板和布局。
class ApplicationMailer < ActionMailer::Base
default from: "from@example.com"
layout false
end
删除原有的 ERB 布局文件后,我们开始为 HTML 和纯文本版本创建 Phlex 布局。
HTML 布局:
class Components::Layouts::Mailers::Html < Phlex::HTML
def view_template(&block)
doctype
html do
head do
meta charset: "utf-8"
end
body do
div do
b { "应用名称" }
end
yield block
div { "此致" }
div { "公司名称" }
end
end
end
end
纯文本布局:
class Components::Layouts::Mailers::Text < Phlex::HTML
def view_template(&block)
plain "应用名称"
plain "\n\n"
yield block
plain "\n\n\n"
plain "此致"
plain "\n"
plain "公司名称"
end
end
在视图中,我们可以利用 around_template
钩子指定具体的布局:
HTML 版本:
class Views::Mailers::Users::Welcome::Html < Views::Base
include Components
def around_template
render Components::Layouts::Mailers::Html.new do
super
end
end
def view_template
div do
plain "我们很高兴您加入我们!感谢您的注册。"
end
a href: "https://example.com" do
plain "点击这里登录"
end
end
end
纯文本版本:
class Views::Mailers::Users::Welcome::Text < Views::Base
inclu