はじめに
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_now
やdeliver_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_now
やdeliver_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
メール本文テンプレート作成
メイラーで作成したインスタンス変数を使って、メール本文を作成します。
公開済の記事数: <%= @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
にアクセスしてブラウザで確認できます。
参考記事


コメント