Использование Rails Action Mailer

В этом кратком руководстве я расскажу о том, как я использую 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. Надеюсь, вы все чему-то научились 🙂

Оцените статью
Procodings.ru
Добавить комментарий