LoginSignup
2
3

More than 1 year has passed since last update.

wheneverとMailerを使って設定日時にメールを送る備忘録

Last updated at Posted at 2022-03-04

wheneverとは

○時になったら○○のコマンドを実行してくれるcronというものがある。このcronの設定を、rubyの簡単な文法で扱えるようにしたライブラリがwhenever
【Rails】wheneverでcronを設定

Mailerとは

Railsには、デフォルトでActionMailerと呼ばれるメール送信機能が備わっている。
通常のテキストメール送信に加え、HTMLメールが送信できたり、添付ファイルを付けるなど様々なオプションがある。

実装内容

毎日am9:00に下記の内容を管理者にメールで送信。

・公開済の記事の総記事数
・昨日公開された記事の件数とタイトル
・メールの件名には「公開済記事の集計結果」と設定
・管理者のメールアドレスにはadmin@example.comを設定
usersテーブルにemailカラムは追加しなくてOK

実装の流れ

1.モデルにスコープを追加
2.メールを作成
3.毎日am9:00にメールを送信できるよう設定

1.モデルにスコープを追加

実装内容の条件の1つに、「昨日公開された記事の件数とタイトル」とあるので、昨日公開された記事をスコープで取得できるようにしておく。

models/article.rb
scope :published_at_yesterday, -> { published.where(published_at: 1.day.ago.all_day) }

2.メールを作成

Mailerは導入済みで進める。
ターミナルにて rails g mailer ArticleMailer を入力。

上記のコマンドで
ActiveMailer::Baseを継承したApplicationMailerArticleMailer(自分で命名したもの)が生成される。

Mailerを作成するとviewのディレクトリとテストも同時に作成される。

app/mailers/application_mailer.rbapp/mailers/article_mailer.rbの2つに内容を記述していく。

app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
  layout 'mailer'
end
app/mailers/article_mailer.rb
class ArticleMailer < ApplicationMailer
    def report_summary
        @published_article_count = Article.published.count
        @articles_published_at_yesterday = Article.published_at_yesterday
                                                                                                #↑先程モデルで定義したスコープ
        mail(to: "admin@example.com",subject: "公開済記事の集計結果")
    end
end

次に、メール本文を記述する。

views/article_mailer/report_summary.text.erb
公開済の記事数: <%= @published_article_count %>件

<% if @articles_published_at_yesterday.present? %>
    昨日公開された記事数: <%= @articles_published_at_yesterday.count %>件
    <% @articles_published_at_yesterday.each do |article| %>
        タイトル: <%= article.title %>
    <% end %>
<% else %>
    昨日公開された記事はありません
<% end %>

configにてメールが直接確認できるよう設定。

config/enviroments/development.rb
config.action_mailer.delivery_method = :letter_opener_web
#↑配信方法を指定している

config.action_mailer.default_url_options = { host: 'localhost:3000' }
#↑アプリケーションのホスト情報をメイラー内で使うためのオプション

2.毎日am9:00にメールを送信できるよう設定

冒頭でwheneverとは○時になったら○○のコマンドを実行してくれる〜と記載したが、まずは実行する 中身rakeタスクにて作成していく。

今回はタスク名をarticle_summaryとして命名。

.ターミナル
$ rails g task article_summary

lib/tasks/article_summary.rakeが作成されるので、この中に定義する。

lib/tasks/article_summary.rake
namespace :article_summary do
  desc "am9時に公開済記事の集計結果を管理者にメールで送る"
    task mail_article_summary: :environment do
      ArticleMailer.report_summary.deliver_now
    end
end

ArticleMailer.report_summary
先程メール作成で定義したもの

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

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


実行する中身を定義できたので、後は「毎日am9:00にメールを送信」を設定すればOK

config/schedule.rb
require File.expand_path(File.dirname(__FILE__) + '/environment')
# Rails.rootを使用するために必要

rails_env = ENV['RAILS_ENV'] || :development
# cronを実行する環境変数

set :environment, rails_env
# cronを実行する環境変数をセット

set :output, "#{Rails.root}/log/cron.log"
# cronのログの吐き出し場所

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

rspec

spec/factories/articles.rb
FactoryBot.define do
  factory :article do
    sequence(:title) { |n| "title-#{n}" }
    sequence(:slug) { |n| "slug-#{n}" }
    category
  end

  trait :published_tomorrow do
    published_at { Time.current.tomorrow }
    state { :publish_wait }
    category
  end

  trait :published_yesterday do
    published_at { Time.current.yesterday }
    state { :published }
    category
  end

  trait :published_two_days_ago do
    published_at { Time.current.ago(2.days) }
    state { :published }
    category
  end
end
spec/mailers/article_mailer_spec.rb
require "rails_helper"

RSpec.describe ArticleMailer, type: :mailer do
  let(:article_tomorrow) { create :article, :published_tomorrow}
  let(:article_yesterday) { create :article, :published_yesterday}
  let(:article_two_days_ago) { create :article, :published_two_days_ago}
  let(:mail) { ArticleMailer.report_summary.deliver_now }
  let(:check_sent_mail) {
    expect(mail.present?).to be_truthy, 'メールが送信されていません'
    expect(mail.to).to eq(['admin@example.com']), 'メールの送信先が正しくありません'
    expect(mail.subject).to eq('公開済記事の集計結果'), 'メールのタイトルが正しくありません'
  }

  describe '公開済記事の集計結果通知メールの送信' do
    context '公開日が2日前の記事が存在する場合' do
      it '公開日が2日前分の記事の結果が送られること' do
        article_tomorrow
        article_yesterday
        article_two_days_ago
        check_sent_mail
        expect(mail.body).to match '2'
        expect(mail.body).to match '公開済の記事数: 2件'
        expect(mail.body).to match 'タイトル: ' + article_yesterday.title
        expect(mail.body).not_to match '昨日公開された記事はありません'
        expect(mail.body).not_to match 'タイトル: ' + article_tomorrow.title
        expect(mail.body).not_to match 'タイトル: ' + article_two_days_ago.title
      end
    end
  end
end

be_truthy
be_truthy / be_falseyを使うと、その仕様にあわせて戻り値の真偽を検証してくれる。
つまり、「trueっぽい値」または「falseっぽい値」かどうかを検証してくれる。

match
正規表現にマッチするかどうかを判断している。

正規表現とは
メタ文字と呼ばれる記号と文字列を組み合わせた構文のことで、検索対象となる文章中から任意の文字列パターンを検索することができます。

参考記事

使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3