В этом кратком руководстве я расскажу о том, как я использую Action Mailer в своих Rails-приложениях. Бонус, если вы владеете основами Rails, знаете HTML/CSS и умеете разворачивать на Heroku (не обязательно, если вы не хотите попробовать почтовую программу в производственной среде). Если вы не умеете делать эти вещи, ничего страшного! Вы все равно сможете следовать за мной, а в конце этого поста я дам ссылки на документацию и несколько других источников, чтобы вы могли начать. Давайте начнем создавать наш почтовик 🙂
Настройка проекта
Чтобы начать, создайте новое приложение Rails 7 (назовите его как хотите, конечно). Я назвал свое mailer-example. rails new mailer-example
. Затем я создал git-репо для отслеживания изменений. Всегда хорошая привычка. Далее мы создадим базовые модели пользователя и заказа. В своем терминале сделайте
rails g scaffold User email:string name:string
Обратите внимание, что в реальном приложении для модели пользователя/аутентификации следует использовать такой гем, как devise. Это просто для демонстрации работы почтового сервера.
Далее добавьте миграцию для добавления user_id
в Orders.
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
В нижней части файла добавьте
В app/views/orders/index.html.erb
внизу файла добавьте
Настройка почтовой программы
Теперь мы готовы к настройке почтовой программы. Мы хотим, чтобы при отправке заказа покупателю отправлялось электронное письмо. Чтобы настроить почтовую программу, запустим терминал.
Это сгенерирует все файлы, необходимые для базовой почтовой программы. Включая тесты и предварительный просмотр. Об этом мы поговорим позже. Сейчас в app/mailers
вы увидите файл под названием order_mailer.rb
. Это тот файл, с которым мы хотим работать. В этом файле мы создадим метод order_email
, который будет содержать переменные экземпляра для поиска пользователя и заказа, а также метод mail для отправки клиенту. Это должно выглядеть следующим образом
# 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
Здесь мы берем пользователя по 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+-.]+@[a-zd-.]+.[a-z]+z/i
validates :email, presence: true, uniqueness: true, format: { with:
VALID_EMAIL_REGEX }
validates :name, presence: true
has_many :orders
end
Не беспокойтесь о regex, вам не нужно его понимать. Он просто проверяет, что письмо отправлено в правильном формате. Мы также уменьшаем размер письма перед сохранением с помощью строки 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
Теперь в нашей модели Order у нас есть обратный вызов после создания заказа для отправки нашего письма. Метод send_order_email
вызывает наш OrderMailer
, который вызывает наш order_email
метод, определенный в OrderMailer
, который вызывает order_email
метод, который мы определили в OrderMailer
. Мы передаем модель Order
с самим собой в строке order: self
. Затем мы передаем метод send_order_email
в метод after_create_commit
.
После этого нам нужен гем, чтобы при отправке письма мы видели, что отправленное письмо открывается в новой вкладке в разработке. Это отличный способ видеть отправляемые письма, не отправляя их на реальный адрес и не загромождая почтовый ящик.
Поместите gem letter_open_web
в свой Gemfile следующим образом:
# Gemfile
group :development, :test do
#Existing gems
#
#
gem "letter_opener_web", "~> 2.0"
end
Затем запустите в терминале bundle install
, чтобы установить гем.
После установки gem вам нужно добавить одну строку в ваш файл development.rb
в config. В /config/environments/development.rb
прокрутите вниз до самого низа файла и добавьте строку:
# config/environments/development.rb
config.action_mailer.delivery_method = :letter_opener
Настройка представлений для нашего почтового клиента
Далее нам понадобится представление для нашего почтовика. Мы создадим два файла: обычный текстовый файл с erb(embedded ruby) и html файл с erb. Представления для почтовой программы будут находиться в 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 mailer не поддерживают внешний 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, набрав в терминале rails c
.
irb(main):001:0>User.create(email: "johnny@example.com", 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, Test Driven Development), но я предпочитаю делать тесты после. Наш первый тест будет проверять, содержит ли письмо то содержимое, которое мы ожидаем. Для начала нам нужно добавить фикстуры, или фиктивные данные, которые будут использоваться тестами. Потому что на самом деле мы не хотим писать в базу данных или делать реальные запросы к ней. Добавьте это в фикстуры users.yml
и orders.yml
. Эти файлы были автоматически сгенерированы, когда мы запустили генератор строительных лесов для обеих моделей.
# test/fixtures/users.yml
one:
email: user@example.com
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
Давайте разберем этот первый тест. Сначала мы настроим тест на использование наших фикстур, созданных в предыдущем шаге. Мы создаем переменную экземпляра, которая использует наши фикстуры Users и Orders. В тестовом блоке мы создаем письмо с помощью нашего OrderMailer
с данными из фикстуры Orders, затем вызываем метод order_email
из нашей OrderMailer
. Далее мы убеждаемся, что будет отправлено только одно письмо с помощью строки assert_emails 1 do
и отправляем письмо. Последние две строки проверяют, что письмо было отправлено нужному пользователю, и что часть тела письма также совпадает. Нас не интересует, совпадает ли содержимое всего тела, это сделало бы тест слишком хрупким. Далее мы напишем тест, чтобы убедиться, что письмо занесено в очередь, когда это должно произойти. Во-первых, нам нужен помощник для нашего теста. Вам понадобится файл orders_helper.rb
в каталоге test/helpers
. Поместите это в 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
, которые были определены в нашем модуле OrdersHelper
из предыдущего шага. Затем мы проверили, было ли зарегистрировано письмо с помощью нашего OrderMailer
, используя метод order_email
, определенный в нашем почтовике. Затем мы передаем ему созданный заказ. Запустив rails test
в терминале, все тесты должны пройти и стать зелеными. зеленый. Запустив наш локальный сервер rails s
, мы можем создать заказ и увидеть, что нам отправлено письмо, которое открывается в другой вкладке, благодаря нашему letter_opener
gem, который мы добавили в начале этого руководства. Наш почтовик завершен! Далее мы заставим наш почтовик работать на рабочем сервере. Если вы не знаете, как развернуть Rails-приложение, можете пропустить следующий раздел.
Отправка почты в продакшене
Если вы не знаете, как развертывать на Heroku, вы можете использовать все, что захотите. Если вы не знаете, как развернуть Rails-приложение в продакшене, можете пропустить этот раздел.
Существует масса способов отправки электронной почты в продакшене. Send Grid, MailGun и многие другие. Самый простой (и бесплатный) способ – использовать gmail. Для того чтобы отправлять электронные письма из нашего приложения с помощью gmail, нам нужно создать пароль приложения – это настройка в нашем аккаунте Google. Здесь мы создадим пароль приложения. Это специальный пароль, который может быть использован нашим приложением для отправки электронных писем без использования пароля аккаунта gmail. Для этого вам необходимо настроить 2FA на своем аккаунте. Поэтому сделайте это, если вы еще не сделали этого. В разделе Вход в Google вы должны увидеть App passwords. Нажмите на него, после чего появится несколько выпадающих окон. Первое – “Выберите приложение”, выберите “Почта”. В разделе Выберите устройство выберите Другое. Появится запрос на имя, вы можете назвать его как угодно. Я использовал mailer-example. Если вы работаете на Heroku, впишите этот пароль в Config Vars. На приборной панели Heroku нажмите settings и найдите Config Vars. Нажмите Reveal Config Vars и добавьте вашу переменную env (я использовал GMAIL_USER_PASSWORD) с паролем, сгенерированным Google. В конце этого поста я привел ссылку на документацию от 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: "username@gmail.com",
password: ENV["GMAIL_USER_PASSWORD"],
authentication: "plain",
enable_starttls_auto: true,
open_timeout: 5,
read_timeout: 5
}
Измените имя_пользователя на ваш адрес электронной почты gmail. Наш пароль защищен в переменной окружения, которую мы сохранили на Heroku. Это был пароль приложения, сгенерированный Google на нашем последнем шаге. Измените домен на любой домен Heroku для сайта. После переноса всего на Heroku все должно работать. Ваше приложение должно отправлять электронные письма как в производственной среде, так и в среде разработки. Потрясающе!
Завершение работы
Вот и все, основной обзор почтовой программы и ее работы. Это очень базовое приложение, но оно предназначено только для демонстрации почтовой программы в Rails. Не стесняйтесь дополнять его. Добавьте devise gem для реальной системы аутентификации пользователей. Добавьте немного стилизации, потому что сейчас приложение выглядит некрасиво LOL. Развивайте Orders и создайте модель Items, в которой вы сможете добавлять товары в заказ. Сделайте из этого полнофункциональный сайт электронной коммерции. Небо – это предел, идите и пишите!
Если вы не знаете, как развернуть Rails на Heroku, вот отличное место для начала. Как использовать конфигурационные параметры Heroku. Если вы не знаете, как использовать git/github, начните здесь. Документация по Git также является хорошим местом для получения информации. Документация по Rails для почтового сервера здесь, а практически все остальное, что вам может понадобиться для Rails, есть в Rails Guides. Надеюсь, вы все чему-то научились 🙂