久々にメール送信の実装を行ったので、メモしておきます。
難しくはないけど、fromに名前入れたり、マルチパートメールにしたりも合わせて対応。
メールのSpecに初挑戦。ActionMailer Previewなるものもあったので、合わせて対応しました。

メーラー作成

先ずは、rails generateで自動生成します。

$ rails g mailer UserMailer
      create  app/mailers/user_mailer.rb
      invoke  erb
      create    app/views/user_mailer
      create    app/views/layouts/mailer.text.erb
      invoke  rspec
      create    spec/mailers/user_mailer_spec.rb
      create    spec/mailers/previews/user_mailer_preview.rb

【参考】ここまでのコミット内容
https://dev.azure.com/nightonly/rails-app-origin/_git/rails-app-origin/commit/619fc531c1ebfd75ff61332381039c0147c26014

コントローラーから呼び出し

アカウント削除の予約や取り消しは、別タスクで実装済なので、メール送信処理のみ加えます。

app/controllers/users/registrations_controller.rb に追加

   # DELETE /users アカウント削除(処理)
   def destroy
     resource.set_destroy_reserve
+    UserMailer.with(user: current_user).destroy_reserved.deliver_now

     Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
     set_flash_message! :notice, :destroy_reserved
     yield resource if block_given?
     respond_with_navigational(resource) { redirect_to after_sign_out_path_for(resource_name) }
  end
   # PUT /users/undo_destroy アカウント削除取り消し(処理)
   def undo_destroy
     resource.set_undo_destroy_reserve
+    UserMailer.with(user: current_user).undo_destroy_reserved.deliver_now

     set_flash_message! :notice, :undo_destroy_reserved
     redirect_to root_path
   end

呼び出されるメーラー

共通化の為、実処理は継承元のApplicationMailerにメソット作って、タイトルのキーのみ渡します。
使用されるメールテンプレは、暗黙的に呼び出し元のメソット名が使われます。

app/mailers/user_mailer.rb に追加

 class UserMailer < ApplicationMailer
+  # アカウント削除受け付けのお知らせ
+  def destroy_reserved
+    send_mail('mailer.user.destroy_reserved.subject')
+  end
+
+  # アカウント削除取り消し完了のお知らせ
+  def undo_destroy_reserved
+    send_mail('mailer.user.undo_destroy_reserved.subject')
+  end
 end

app/mailers/application_mailer.rb に追加

class ApplicationMailer < ActionMailer::Base
   layout 'mailer'

+  private
+
+  # メール送信
+  def send_mail(subject_key)
+    @user = params[:user]
+    mail(from: "\"#{Settings['mailer_from']['name'].gsub(/%{app_name}/, t('app_name'))}\" <#{Settings['mailer_from']['email']}>",
+         to: @user.email,
+         subject: t(subject_key).gsub(/%{app_name}/, t('app_name')))
+  end
end

定数定義

メーラーで呼び出している定数を定義します。

config/settings/development.yml に追加

mailer_from:
  name: '%{app_name}'
  email: 'noreply@localhost'

※test.ymlやproduction.ymlも同様に定義

config/locales/ja.yml に追加

 ja:
   app_name: "RailsAppOrigin"
+    user:
+      destroy_reserved:
+        subject: "【%{app_name}】アカウント削除受け付けのお知らせ"
+      undo_destroy_reserved:
+        subject: "【%{app_name}】アカウント削除取り消し完了のお知らせ"

メール本文

メーラーで使用される本文のテンプレを追加します。

app/views/user_mailer/destroy_reserved.html.erb を作成

<%= @user.email %> 様<br/>
<%= t('app_name') %> をご利用頂きありがとうございます。<br/>
<br/>
アカウント削除を受け付けました。またのご利用をお待ちしております。<br/>
<br/>
このアカウントは<%= l(@user.destroy_schedule_at.to_date) %>以降に削除されます。それまでは取り消し可能です。<br/>
削除されるまではログインできますが、一部機能が制限されます。<br/>
<%= link_to 'アカウント削除取り消し', users_undo_delete_url %><br/>

text.erbを作成すると、マルチパートメールになります。
app/views/user_mailer/destroy_reserved.text.erb を作成

<%= @user.email %> 様
<%= t('app_name') %> をご利用頂きありがとうございます。

アカウント削除を受け付けました。またのご利用をお待ちしております。

このアカウントは<%= l(@user.destroy_schedule_at.to_date) %>以降に削除されます。それまでは取り消し可能です。
削除されるまではログインできますが、一部機能が制限されます。

アカウント削除取り消し
<%= users_undo_delete_url %>

Tips: 最初、xxx_urlをxxx_pathと書いてしまいエラーになりました。
 スキーマ+ドメインがないとアクセスできないので当然で、よく考えると親切ですね。

app/views/user_mailer/undo_destroy_reserved.html.erb を作成

<%= @user.email %> 様<br/>
<br/>
<%= t('app_name') %> をご利用頂きありがとうございます。<br/>
<br/>
アカウント削除の取り消しが完了しました。<br/>

上記と同様に、text.erbを作成すると、マルチパートメールになります。
app/views/user_mailer/undo_destroy_reserved.text.erb を作成

<%= @user.email %> 様

<%= t('app_name') %> をご利用頂きありがとうございます。

アカウント削除の取り消しが完了しました。

動作確認

$ rails s
-> http://localhost:3000/

Spec実装

初めてなので、テスト駆動ではなく、後から書きました。

spec/mailers/user_mailer_spec.rb に追加

  # アカウント削除受け付けのお知らせ
  describe 'destroy_reserved' do
    let(:user) { FactoryBot.create(:user, destroy_schedule_at: Time.current + Settings['destroy_schedule_days'].days) }
    let(:mail) { UserMailer.with(user: user).destroy_reserved }
    it '送信者のメールアドレスが設定と一致' do
      expect(mail.from).to eq([Settings['mailer_from']['email']])
    end
    it '宛先がユーザーのメールアドレスと一致' do
      expect(mail.to).to eq([user.email])
    end
    it '本文(html)にアカウント削除取り消しのURLが含まれる' do
      expect(mail.html_part.body).to include("\"#{users_undo_delete_url}\"")
    end
    it '本文(text)にアカウント削除取り消しのURLが含まれる' do
      expect(mail.text_part.body).to include(users_undo_delete_url)
    end
  end

  # アカウント削除取り消し完了のお知らせ
  describe 'undo_destroy_reserved' do
    let(:user) { FactoryBot.create(:user) }
    let(:mail) { UserMailer.with(user: user).undo_destroy_reserved }
    it '送信者のメールアドレスが設定と一致' do
      expect(mail.from).to eq([Settings['mailer_from']['email']])
    end
    it '宛先がユーザーのメールアドレスと一致' do
      expect(mail.to).to eq([user.email])
    end
  end

動作確認

$ rspec spec/mailers/user_mailer_spec.rb 
6 examples, 0 failures

ActionMailer Preview

追伸:FactoryBot.createだとTruncateされないので、build_stubbedに変更しました。
[rails s]で動くので、testではなくdevelopmentのDBですね。
seedする筈ではあるが、必ずユーザーが居るとは限らないので、FactoryBotは使いたい。

spec/mailers/previews/user_mailer_preview.rb に追加

  # アカウント削除受け付けのお知らせ
  def destroy_reserved
    user = FactoryBot.create(:user, destroy_schedule_at: Time.current + Settings['destroy_schedule_days'].days)
    user = FactoryBot.build_stubbed(:user, destroy_schedule_at: Time.current + Settings['destroy_schedule_days'].days)
    UserMailer.with(user: user).destroy_reserved
  end

  # アカウント削除取り消し完了のお知らせ
  def undo_destroy_reserved
    user = FactoryBot.create(:user)
    user = FactoryBot.build_stubbed(:user)
    UserMailer.with(user: user).undo_destroy_reserved
  end

動作確認

送信しなくても、見れるので、作成・変更した時の確認に便利ですね。
宛先と本文のメールアドレスが異なるけど、iframe使ってて、別々にリクエストされるから。
(大事な所ではないので、拘らずにこのままにしました)

$ rails s
-> http://localhost:3000/rails/mailers

導線追加

リンクがページにあると便利なので、フッターの下に追加。開発環境のみ表示。
序でにLetterOpenerWebも。

app/views/layouts/application.html.erb に追加

<% if Rails.env.development? %>
    <ul>
      <li><%= link_to '[LetterOpenerWeb]', letter_opener_web_path, target: :_blank, rel: [:noopener, :noreferrer] %></li>
      <li><%= link_to '[ActionMailer Preview]', '/rails/mailers', target: :_blank, rel: [:noopener, :noreferrer] %></li>
    </ul>
<% end %>

Tips: noopenerとnoreferrerは、セキュリティとパフォーマンスの為に追加した方がいい。
 (自サイト内なので、気にしなくても大丈夫ですが、念の為、入れてます)
 参考: グーグルのエンジニアが警告、「別タブで開く」リンクは実はヤバいんだって!?

【参考】ここまでのコミット内容
https://dev.azure.com/nightonly/rails-app-origin/_git/rails-app-origin/commit/e6db5bd50dbbbebce41cc6458b5b10549d40fbf0

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です