在这个快速教程中,我将介绍如何在我的 Rails 应用程序中使用 Action Mailer。如果您有一些 Rails 基础知识、HTML/CSS 知识以及如何部署到 Heroku(除非您想在生产环境中尝试邮件程序,否则不需要)。如果你不知道如何做这些事情,没关系!您仍然可以继续跟进,我将在本文末尾链接文档和其他一些资源以帮助您入门。让我们开始构建我们的邮件程序:)
项目设置
首先,创建一个新的 Rails 7 应用程序(当然可以随意命名)。我将我的邮件命名为示例。 rails new mailer-example
。接下来我创建了一个 git repo 来跟踪更改。总是一个好习惯。接下来我们将搭建基本的用户和订单模型。在你的终端做
rails g scaffold User email:string name:string
rails g scaffold Order item:string price:integer description:text
请注意,您应该在真实应用程序中使用类似设计的 gem 来进行用户模型/身份验证。这只是邮件程序如何工作的基本演示。
接下来添加迁移以将user_id
添加到 Orders。
rails g migration AddUserIdToOrders
Rails 会自动为我们蛇行。您应该会看到在db/migrate
中创建的迁移文件。它将类似于20220524005557_add_user_id_to_orders
。这些数字是一个时间戳,所以你的会有所不同,但其余的应该是一样的。
这是迁移文件的样子
# db/migrate/<timestamp>_add_user_id_to_orders.rb class AddUserIdToOrders < ActiveRecord :: Migration [ 7.0 ] def change add_column :orders , :user_id , :integer add_index :orders , :user_id end end
我们还需要添加一个迁移来索引用户的电子邮件,这样我们就可以确保它们在数据库级别是唯一的。执行rails g migration AddIndexToUsersEmail
并在该迁移文件中
# db/migrate/<timestamp>_add_index_to_users_email.rb class AddIndexToUsersEmail < ActiveRecord :: Migration [ 7.0 ] def change add_index :users , :email , unique: true end end
保存文件,然后运行rails db:migrate
迁移数据库。
接下来在config/routes.rb
中将订单页面设为根路由。你的routes.rb
应该是这样的
# config/routes Rails . application . routes . draw do root to: "orders#index" resources :orders resources :users end
接下来,我们将在 User 和 Order 模型索引视图中添加一个链接,以便在两者之间轻松导航。我们的用户索引视图位于app/views/users/index.html.erb
文件的底部添加<%= link_to "Orders", orders_path %>
在app/views/orders/index.html.erb
放在文件的底部<%= link_to "Users", users_path %>
设置邮件程序
现在我们准备设置一个邮件程序。我们希望在客户提交订单时向他们发送一封电子邮件。要设置邮件程序,我们将在终端中运行rails g mailer OrderMailer
这将生成基本邮件程序所需的所有文件。包括测试和预览。我们将谈论即将到来的。现在,在app/mailers
中,您将看到一个名为order_mailer.rb
的文件。这是我们要使用的文件。在这个文件中,我们创建了一个名为order_email
的方法,该方法将包含用于查找用户和订单的实例变量以及发送给客户的邮件方法。它应该看起来像这样
# app/mailers/order_mailer.rb class OrderMailer < ApplicationMailer def order_email @order = Order . last @user = User . find_by ( id: @order . user_id ) mail ( to: @user . email , subject: "Thanks for your order #{ @user . name } !" ) end end
这里我们通过 Order 中的user_id
来抓取用户,因此为订单抓取了正确的用户。我们获取最后创建的订单,然后使用 mail 方法将电子邮件发送给带有主题的用户。我们还使用字符串插值将用户名动态放入主题中。
设置我们的模型
现在我们需要在我们的 User 和 Order 模型中添加几行来进行一些基本的验证。在app/models/user.rb
添加以下行并保存文件。这个演示不是完全必要的,但它也没有伤害。
# app/models/user.rb class User < ApplicationRecord before_save { email . downcase! } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[az\d\-.]+\.[az]+\z/i validates :email , presence: true , uniqueness: true , format: { with: VALID_EMAIL_REGEX } validates :name , presence: true has_many :orders end
不要担心正则表达式,你不需要理解它。它只是确保提交了正确的电子邮件格式。我们还在使用before_save
行保存电子邮件之前将其小写。我们还确保存在时字段不能为空白presence: true
且电子邮件必须是唯一的。
现在在app/models/order.rb
我们需要添加一些验证,与我们的User
模型的关联,但更重要的是,我们需要添加一些关键行来使我们的邮件程序工作。核实
# app/models/order.rb class Order < ApplicationRecord validates :item , presence: true validates :price , presence: true validates :description , presence: true belongs_to :user after_create_commit :send_order_email def send_order_email OrderMailer . with ( order: self ). order_email . deliver_later end end
现在在我们的订单模型中,我们在创建订单后有一个回调来发送我们的电子邮件。 send_order_email
方法调用了我们的OrderMailer
,它调用了order_email
中定义的OrderMailer
方法,该方法调用了我们在OrderMailer
中定义的order_email
方法。我们在order: self
行中传递带有自身的Order
模型。然后我们将send_order_email
方法传递给after_create_commit
方法。
完成此操作后,我们需要一个 gem,因此当发送电子邮件时,我们会看到发送的电子邮件在开发中的新选项卡中打开。这是查看正在发送的电子邮件的好方法,而无需实际将电子邮件发送到真实地址并弄乱邮箱。
将 gem letter_open_web
放入您的 Gemfile 中,如下所示:
# Gemfile group :development , :test do #Existing gems # # gem "letter_opener_web" , "~> 2.0" end
然后在您的终端bundle install
中运行以安装 gem
安装 gem 后,您需要在 config.rb 文件中添加一行到您的development.rb
文件中。在/config/environments/development.rb
中向下滚动到文件底部并添加以下行:
# config/environments/development.rb config . action_mailer . delivery_method = :letter_opener
为我们的 Mailer 设置视图
接下来我们需要我们的邮件程序的视图。我们将制作两个文件,一个是带有 erb(嵌入式 ruby)的纯文本文件,另一个是带有 erb 的 html 文件。邮件程序的视图将位于app/views/order_mailer
中。在您的终端中运行touch app/views/order_mailer/order_email.html.erb && touch app/views/order_mailer/order_email.text.erb
。这将创建我们需要的两个视图文件。使您的 html.erb 文件如下
<!-- app/views/order_mailer_order_email.html.erb --> <h1> Thank you for your order, < %= @ user.name % > ! </h1> <p> Below are the details of your order </p> <table> <tr> <th> Item </th> <th> Price </th> <th> Description </th> </tr> <tr> <td>< %= @ order.item % ></td> <td>< %= @ order.price % ></td> <td>< %= @ order.description % ></td> </tr> </table> <style> td , th { border : 1px solid #dddddd ; text-align : left ; padding : 8px ; } table { font-family : arial , sans-serif ; border-collapse : collapse ; width : 100% ; } th { padding-top : 12px ; padding-bottom : 12px ; text-align : left ; background-color : #04AA6D ; color : white ; } </style>
我们使用<style>
标签是因为 Rails 邮件视图,因为它们不支持外部 CSS 。你也可以做内联样式。接下来是text.erb
文件
<!-- app/views/order_mailer_order_email.text.erb --> Thank you for your order, < %= @ user.name % > ! =============================================== Here are the details of your order Item: < %= @ order.item % > Price: < %= @ order.price % > Description: < %= @ order.description % >
为我们的邮件添加预览
此时,我们的邮件程序应该可以工作了。在尝试之前,我们将首先对其进行预览。我们之前运行的生成器使我们的邮件程序已经为我们生成了这个文件。它应该在test/mailers/previews/order_mailer_preview.rb
中。在这个文件中,我们将创建一个名为order_email
的方法。它将第一个用户拉出数据库和第一个订单,这样它就有数据来填充预览。把它放在你的order_mailer_preview.rb
文件中。
# test/mailers/previews/order_mailer_preview.rb class OrderMailerPreview < ActionMailer :: Preview def order_email OrderMailer . with ( user: User . first ). order_email end end
现在一切都应该很好!但是,在我们添加一些数据之前,预览将不起作用。它无法渲染数据库中没有用户或订单的模板,所以让我们添加一个用户和一个订单!我们可以启动服务器并通过站点执行此操作,但我将在此处的控制台中执行此操作。如果您愿意,您可以通过该网站进行操作。如果没有,通过在终端中输入rails c
来启动 rails 控制台
irb(main):001:0> User . create ( email: "[email protected]" , name: "Johnny" ) irb(main):001:0> Order . create ( item: "NBA Jersey" , price: 110 , description: "NBA Jersey for Joel Embiid" ) irb(main):001:0> exit
现在添加了这些数据,在终端中使用rails s
启动服务器。接下来,您可以转到localhost:3000/rails/mailers
,您将看到我们的 Order Mailer 和我们的order_email
方法。单击order_email
,您应该会看到我们电子邮件的预览。您可以在 HTML 和纯文本预览之间切换。
向我们的邮件程序添加测试
现在我们将添加测试以确保 1. 我们的邮件在应该排队的时候(下订单后)和 2. 电子邮件包含我们期望的内容。由于预览有效,我们应该能够编写通过测试。如果您启动服务器并下一个新订单,您应该会收到在新选项卡中打开的电子邮件。一切都应该工作,但我们将编写测试来支持它,因此我们不必在每次更改邮件系统时手动测试邮件。每次对邮件系统进行更改时,手动测试邮件程序以查看它是否仍然有效,变得缓慢而乏味,快速。这就是测试的用武之地。我们可以先编写测试并开发我们的邮件程序,直到它们通过(称为 TDD,测试驱动开发),但我更喜欢在之后进行测试。我们的第一个测试是查看电子邮件是否包含我们期望的内容。首先,我们需要添加夹具,也就是虚拟数据,供测试使用。因为我们实际上并不想写入数据库或对数据库进行实际查询。将此添加到users.yml
和orders.yml
固定装置。当我们为两个模型运行脚手架生成器时,这些文件是自动生成的。
# test/fixtures/users.yml one : email : [email protected] name : Example id : 1
# test/fixtures/orders.yml one : item : Item price : 20 description : Description user_id : 1
现在有了我们的固定装置设置,我们可以开始编写我们的测试。我们将编写第一个测试,看看电子邮件是否包含我们期望的内容。
# test/mailers/order_mailer_test.rb require "test_helper" class OrderMailerTest < ActionMailer :: TestCase setup do @order = orders ( :one ) @user = users ( :one ) end test "send order details email to customer" do email = OrderMailer . with ( order: @order ). order_email assert_emails 1 do email . deliver_now end assert_equal [ @user . email ], email . to assert_match ( /Below are the details of your order/ , email . body . encoded ) end end
让我们分解第一个测试。因此,首先我们设置测试以使用在上一步中创建的固定装置。我们创建了一个使用我们的用户和订单装置的实例变量。在测试块中,我们使用OrderMailer
创建一封电子邮件,其中包含来自 Orders 夹具的数据,然后我们从OrderMailer
调用order_email
方法。接下来,我们确保仅使用assert_emails 1 do
发送一封电子邮件,然后我们发送电子邮件。最后两行检查电子邮件是否发送给正确的用户,并且正文的那部分也匹配。我们不关心它是否匹配整个身体的内容,这会使测试变得过于脆弱。接下来,我们将编写一个测试,以确保电子邮件在应有的时间入队。首先,我们需要一个帮手来进行我们的测试。您将需要在test/helpers
目录中创建文件orders_helper.rb
。把它放在orders_helper.rb
# test/helpers/orders_helper.rb module OrdersHelper def order_attributes { user: users ( :one ), item: "Item" , price: 30 , description: "Description" } end end
这里我们使用帮助程序而不是我们的 yaml 文件,因为在分配属性时,您必须将哈希作为参数传递。如果您尝试通过我们的订单夹具,测试将失败并出现错误。使用我们的助手,我们现在可以编写测试来查看当有新订单时是否有电子邮件入队。
# test/models/order_test.rb require "test_helper" require "helpers/orders_helper" class OrderTest < ActiveSupport :: TestCase include ActionMailer :: TestHelper include OrdersHelper test "sends email when order is created" do order = Order . create! ( order_attributes ) assert_enqueued_email_with OrderMailer , :order_email , args: { order: order } end end
让我们回顾一下代码。我们像往常一样拥有我们的test_helper
。然后我们拉入我们在最后一步中创建的助手。在我们的课程中,我们引入了ActionMailer::TestHelper
来使用assert_enqueued_email_with
方法,当然我们也包括了OrdersHelper
。接下来是实际测试,我们使用order_attributes
创建一个订单,该 order_attributes 在上一步的模块OrdersHelper
中定义。然后我们使用我们的邮件程序中定义的order_email
方法检查是否有一封电子邮件加入了我们的OrderMailer
队列。然后我们将创建的订单传递给它。在终端运行rails test
,所有测试都应该通过并且是green 。启动我们的本地服务器rails s
,我们可以创建一个订单并看到我们收到一封发送的电子邮件,该电子邮件在另一个选项卡中打开,这要感谢我们在本教程开始时添加的letter_opener
gem。我们的邮件完成了!接下来,我们将让我们的邮件程序在生产服务器上工作。如果您不知道如何部署 Rails 应用程序,请随意跳过下一部分。
在生产中发送邮件
如果你不知道如何部署到 Heroku,你可以使用任何你想要的东西。如果您不知道如何将 Rails 应用程序部署到生产环境中,可以跳过本节。
有很多方法可以在生产环境中发送电子邮件。发送网格、MailGun 等等。最简单(也是免费的方式)是使用 gmail。为了使用 gmail 从我们的应用程序发送电子邮件,我们需要在我们的 Google 帐户中设置应用程序密码。在这里,我们将创建一个应用程序密码。这是一个特殊的密码,可用于我们的应用程序发送电子邮件,而无需实际使用我们的 gmail 帐户密码。为此,您需要在您的帐户上设置 2FA。如果你还没有这样做,那就这样做吧。在登录 Google 下,您应该会看到应用密码。单击它,然后您将看到一些下拉菜单。第一个,选择应用程序,选择邮件。在选择设备下,选择其他。它会要求一个名字,你可以随意命名。我使用了邮件示例。如果你在 Heroku 上,把这个密码放在你的 Config Vars 中。在 Heroku 仪表板上单击设置并查找 Config Vars。单击 Reveal Config Vars 并使用 Google 生成的密码添加您的环境变量(我使用 GMAIL_USER_PASSWORD)。我在这篇文章的末尾链接了 Heroku 的文档,如果你遇到困难,如何使用 Config Vars。
下一步
我们需要设置我们的生产配置以在生产中使用 gmail。我们需要编辑我们的production.rb
并添加以下内容:
# config/environments/production.rb config . action_mailer . delivery_method = :smtp config . action_mailer . smtp_settings = { address: "smtp.gmail.com" , port: 587 , domain: "example.com" , user_name: "[email protected]" , password: ENV [ "GMAIL_USER_PASSWORD" ], authentication: "plain" , enable_starttls_auto: true , open_timeout: 5 , read_timeout: 5 }
将您的用户名更改为您的 gmail 电子邮件。我们的密码受保护在一个环境变量中,我们保存在 Heroku 上,这是我们在最后一步由 Google 生成的应用程序密码。将域更改为您的 Heroku 域用于该站点的任何内容。将所有内容推送到 Heroku 后,一切都应该正常工作。您的应用应在生产和开发环境中发送电子邮件。惊人的!
收拾东西
以上就是邮件程序的基本概述及其工作原理。这是一个非常基本的应用程序,但它只是为了演示 Rails 中的邮件程序。随意添加。为真正的用户身份验证系统添加设计gem。添加一些样式,因为该应用程序目前很丑 LOL。建立在 Orders 之上并创建一个 Items 模型,您可以在其中将项目添加到订单中。将其构建为功能齐全的电子商务网站。天空是极限,继续编码!
如果您不知道如何将 Rails 部署到 Heroku ,这里是一个很好的起点。如何使用Heroku 配置变量。如果您不知道如何使用 git/github,请从这里开始。 Git 文档也是获取信息的好地方。这里的邮件程序的 Rails 文档以及 Rails 所需的几乎所有其他内容都在 Rails Guides 上。希望你们都学到了一些东西:)