[Rails] ActionMailerとWheneverとRakeタスクを使ってメールを定期送信する

学習記録

はじめに

ActionMailerとWheneverとRakeタスクを使って、毎日am9:00に管理者へメールを送信する機能を実装させたいです。

メイラー作成

Action Mailerとは

Railsにはメールを送る仕組みとしてAction Mailer(メイラー)が用意されています。メイラーはコントローラと似ています。 コントローラがテンプレートを通じて画面を出力するように、メイラーはテンプレートを通じてメールを作成、送信することができます。

メイラー生成

以下のようにジェネレータを使ってメイラーを生成します。 名前はArticleMailerとします。

> bundle exec rails g mailer ArticleMailer                                                                       10_mail_summary
      create  app/mailers/article_mailer.rb
      create  app/mailers/application_mailer.rb
      invoke  slim
      create    app/views/article_mailer
      create    app/views/layouts/mailer.text.slim
      create    app/views/layouts/mailer.html.slim
      invoke  rspec
      create    spec/mailers/article_mailer_spec.rb
      create    spec/mailers/previews/article_mailer_preview.rb

複数のファイルが同時に生成されました。

メイラー編集

生成されたメイラーに、公開済記事についてのメールを送るためのメソッドを追加します。 report_summaryという名前のメソッドにします。

class ArticleMailer < ApplicationMailer
  def report_summary
    @published_article_count = Article.published.count
    @articles_published_yesterday = Article.published_at_yesterday
    mail(to: 'admin@example.com', subject: '公開済記事の集計結果')
  end
end

@published_article_count: 全ての記事の中で公開されている記事の数を出しているものが入る。インスタンス変数にすることでメール本文で使うことができる。
@articles_published_at_yesterday: は全ての記事の中で公開日が昨日のものが入る。インスタンス変数にすることでメール本文で使うことができる。
to: 'admin@example.com': 送信先の指定。
subject: '公開済記事の集計結果': 題名の指定。
published_at_yesterday: 別途scopeで定義する必要あり。

scope作成

# GOOD
scope :published_yesterday, -> { where('published_at: 1.day.ago.all_day') }

# NG
scope :published_yesterday, -> { where(published_at: Date.yesterday) }

1日全体を表すRangeは Time#all_day で取得します。

メール送信メソッドのdeliverとdeliver_nowとdeliver_laterの違いと使い分け

report_summaryメソッドは、**ActionMailer::MessageDelivery**オブジェクトを返します。

[1] pry(main)> ArticleMailer.report_summary.class
=> ActionMailer::MessageDelivery

このオブジェクトは、そのメール自身が送信対象であることをdeliver_nowdeliver_laterに伝えます。 またActionMailer::MessageDeliveryオブジェクトは、Mail::Messageをラップしています。 仮に内部のMail::Messageオブジェクトの表示や変更などを行いたい場合は、ActionMailer::MessageDeliveryオブジェクトのmessageメソッドにアクセスします。

deliver
ArticleMailer.report_summary.deliver

ActionMailer::MessageDeliveryにて、deliverはRails4.2では非推奨となり、Rails5.0からは削除されています。(Rails 4.2からActive Jobが導入され、後述の非同期処理のdeliver_laterが追加されました。それにより同期処理の名前がこれまでのdeliverからdeliver_nowに置き換わったので、Rails4.2では非推奨となりRails5.0からは削除された経緯があります。)

上のコード例の場合、Mail::Message#deliverが呼ばれています。

def deliver
  inform_interceptors
  if delivery_handler
    delivery_handler.deliver_mail(self) { do_delivery }
  else
    do_delivery
  end
  inform_observers
  self
end

Mail::Message#deliverを直接呼ぶのではなく、deliver_nowdeliver_laterのようなActionMailer::MessageDeliveryのメソッドを実行しましょう。

deliver_now
ArticleMailer.report_summary.deliver_now

deliver_nowは非同期ではなく、即時にメールを送信するメソッドなので、メールを送信し終えるまで次の行は実行されません。 railsコンソールやrakeタスクなど、すぐに処理を実行して終了まで待ちたいときに使います。

# すぐにメール送信したい場合は#deliver_nowを使用
UserMailer.welcome(@user).deliver_now

# Active Jobを使用して後でメール送信したい場合は#deliver_laterを使用
UserMailer.welcome(@user).deliver_later
def deliver_now
  processed_mailer.handle_exceptions do
    message.deliver
  end
end

例外処理をしている中でMail::Message#deliverを実行しています。同じ同期処理のdeliverではなくdeliver_nowを使用するようにしましょう。

deliver_later

Active Jobを使用して非同期でメールを送信するメソッドです。メールの送信処理を待たなくても、次の行を実行できるということです。 よってユーザーはメール送信という時間のかかる処理を待たずに、次の画面を見ることができます。

def deliver_later(options = {})
  enqueue_delivery :deliver_now, options
end

メール本文テンプレート作成

メイラーで作成したインスタンス変数を使って、メール本文を作成します。

/article_mailerディレクトリはジェネレートした際に生成されましたが、中身のファイルは別途作成する必要があります。 間違ってもapp/views/layouts/mailer.text.slim などのレイアウトファイルを削除したり変更してはいけません。

公開済の記事数: <%= @published_article_count %>件

<% if @articles_published_yesterday.present? %>                           # 昨日公開された記事が存在する場合、
  昨日公開された記事数: <%= @articles_published_yesterday.count %>件        # 件数を表示
  <% @articles_published_yesterday.each do |article| %>           # each文で回し、
    タイトル: <%= article.title %>                                 # タイトルを表示
    公開日時: <%= l(article.published_at) %>                       # 公開日時を表示
  <% end %>
<% else %>                                                        # 昨日公開された記事が存在しない場合、
  昨日公開された記事はありません   
<% end %>

HTMLタグなどを書かないよう注意しましょう。 他のViewファイル同様レイアウトファイルが指定されているので、書いてしまうと重複になってしまいます。

class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'                  # 送り元のデフォルト値
  layout 'mailer'
end
= yield

メイラーのプレビュー機能

ここまで実装したメイラーとビューの動作を、プレビュー機能を使って確認してみましょう。 プレビュー機能は、spec/mailers/previews/article_mailer_preview.rbを使います。このファイルは前に実行したgenerateコマンドで生成されています。

# Preview all emails at http://localhost:3000/rails/mailers/article_mailer
class ArticleMailerPreview < ActionMailer::Preview
  def report_summary
    ArticleMailer.report_summary
  end
end

そしてrails serverを起動して、http://localhost:3000/rails/mailers/article_mailer へアクセスして確認できます。

Rakeタスク作成

cronを使用して、定期的に実行する処理をRakeタスクの中にまとめたいので、ジェネレートコマンドで生成します。

$ bundle exec rails g task article_summary                                                                      ?10_mail_summary
      create  lib/tasks/article_summary.rake
namespace :article_summary do
  desc '管理者に対して総記事数、昨日公開された記事があれば記事数とタイトルをメールで送信'
  task mail_article_summary: :environment do
    ArticleMailer.report_summary.deliver_now
  end
end

namespace: ファイル名と同じにする
desc: タスクの説明文
task: taskの処理名。自分で命名する。

wheneverの導入

Gemfileに書きます。

gem 'whenever', require: false

アプリケーションから使わない gem は require: falseを付与させます。 読み込まないようにして起動を少しでも早くするためや、想定外の挙動を減らすためなどが理由です。

Wheneverの設定ファイル生成

以下のコマンドでconfig/schedule.rb が生成されます。こちらのファイルの中に定期実行したいタスクなどを書き込んで反映させます。

$ wheneverize .

Wheneverの設定

every 1.day, at: '9am' do
  rake 'article_summary:mail_article_summary'
end

whemeverのコマンド解説

wheneverでは次の4つをスケジューリングできます。

command: bashコマンド実行
rake: rakeタスク実行
runner: Rails内のメソッド実行
script: scriptの実行

crontabを実行

crontabを使ってcronに対して命令をします。

$ bundle exec whenever --update-crontab   # wheneverの設定更新

$ bundle exec whenever  # 設定内容にエラーが無いか確認

$ crontab -l  # 設定されているcronを確認

$ bundle exec whenever --clear-crontab  # crontabの設定を削除

letter-openerで確認する

インストール

gem 'letter_opener_web'
$ bundle install

ルーティング設定

if Rails.env.development?
  mount LetterOpenerWeb::Engine, at: '/letter_opener'
end

設定ファイル編集

config.adction_mailer.delivery_method = :letter_opener_web
config.action_mailer.default_url_options = { host:  'localhost:3000' }

rails s でサーバーを起動し、locaohost:3000/letter_opener にアクセスしてブラウザで確認できます。

参考記事

Active Job の基礎 - Railsガイド
バックグラウンドで動作するジョブの作成・キュー送信・実行方法に必要なすべてを解説します。
Action Mailer の基礎 - Railsガイド
Action Mailerを使用してメールを送受信する方法について解説します。
GitHub - javan/whenever: Cron jobs in Ruby
Cron jobs in Ruby. Contribute to javan/whenever development by creating an account on GitHub.

コメント

タイトルとURLをコピーしました