はじめに
個人開発において、ユーザーにリマンダーメールを送信したいと思ったのですが、体系的にまとめられているドキュメントを見つけるのに苦労しました。
自分と同じような初学者だとつまずく部分もあるかと思いますので、本記事でモデルの作成から順に説明していきます。
やりたいこと
- 毎日、日本時刻AM9:00に、開始時刻が次の日になっているスケジュール(以下、イベントと言います。)を登録したユーザーにメールでリマインドする。
- 各ユーザーは、リマインド機能のオンオフを切り替えられる。(初期状態はオン)
- メール送信側はGmailを使用
開発環境
Ruby 3.1.2
Rails 6.1.6
実装
【モデル】
今回、必要となるモデルは、以下の2つです。
-
ユーザー(User)モデル:どのメールアドレスに送信するのか、リマインド機能のオンオフ(deviseを使用)
- name(ユーザー名) :string
- email_receiving_activation(リマインド機能のオンオフ) :boolean
- メールアドレスやパスワードはdeviseで自動生成される
-
イベント(Event)モデル:ユーザーが登録したイベントと、その開始時刻を保存する
- title(イベント名) :string
- start_at(開始時刻) :datetime
- user(アソシエーション) :references
rails g devise User name:string email_receiving_activation:boolean
rails g model Event title:string start_at:datetime user:references
モデルを作成したら、マイグレーションファイルへのNotNull制約やデフォルト値の設定、モデルファイルへのアソシエーションを記述します。
class DeviseCreateUsers < ActiveRecord::Migration[6.1]
def change
create_table :users do |t|
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
t.string :name, null: false, default: ""
t.boolean :email_receiving_activation, null: false, default: true # 初期状態はtrue(リマインダー機能がオン)の状態
...
end
...
end
end
class CreateEvents < ActiveRecord::Migration[6.1]
def change
create_table :events do |t|
t.references :user, null: false, foreign_key: true
t.string :title, null: false
t.datetime :start_at, null: false
end
end
end
rails db:migrate
class User < ApplicationRecord
has_many :events, dependent: :destroy
end
class Event < ApplicationRecord
belongs_to :user
end
【タイムゾーンの変更】
デフォルトで設定されているタイムゾーンはUTC(協定世界時)になっています。これを日本時間に変更しておきましょう。
module Portfolio
class Application < Rails::Application
...
config.time_zone = "Asia/Tokyo" # これを追加
...
end
end
【データの作成】
次にデータを作成します。WEBアプリケーションとしては、コントローラやビューを用いてデータを作成するのが一般的ですが、今回は本質と異なるため、その工程を省き、コンソールでデータを作成します。
irb(main):001:0> User.create(name: "test_user", email: "test_user@email", password: "password")
=> #<User id: 1, name: "test_user", email: "test_user@email", email_receiving_activation: true, created_at: "2022-08-23 10:45:50.800011000 +0900", updated_at: "2022-08-23 10:45:50.800011000 +0900">
irb(main):002:0> Event.create(title: "test_event", start_at: "2022-08-31 13:00:00.000000000 JST +09:00"", user_id: 1)
=> #<Event:########
id: 1,
user_id: 1,
title: "test_event",
start_at: " Wed, 31 Aug 2022 13:00:00.000000000 JST +09:00">
これで、ユーザーと2022年8月31日13時のイベントが作成できました。
【メールの送信設定】
メールを送信するためには、Action Mailerという仕組みを活用します。
まず、ターミナルでMailerを作成します。
rails g mailer RemaindEventMailer
これでapp/mailers/remaind_event_mailer.rb
が作成されますので、その中にメールを送信するメソッドを記述してください。
class RemaindEventMailer < ApplicationMailer
# ↓ここから追加
def creation_email(event)
@event = event # メールのテンプレを作成するときに使用します
mail(
subject: "【Converce】イベントリマインダー", # メールの件名
to: event.user.email, # メールの宛先
)
end
end
次にユーザーに送信されるメールのテンプレートを作成します。
app/views/remaind_event_mailer
にcreation_email.html.erb
を作成し、自分の好きなようにテンプレートを記述してください。一例を以下に示しておきます。
app/mailers/remaind_event_mailer.rb
で@event
を定義しているので、titleやstart_atを表示することができます。
<p>登録されたイベントのご予定日が近づいて参りましたので、ご連絡いたします。</p>
<h2><%= @event.title %></h2>
<p>
<span>●開始予定日時:<%= @event.start_at.strftime('%Y/%m/%d %H:%M') %></span><br>
</p>
その次に、今回はGmailからメールを送信するので、そのための設定を行います。
まずは、アプリパスワードというものが必要となるため、Gmail側でそちらを取得します。手順は、こちらの記事が非常に参考になります。
アプリパスワードの取得が完了したら、Rails側の設定を行います。以下のように記述してください。
require "active_support/core_ext/integer/time"
Rails.application.configure do
...
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.gmail.com",
domain: "gmail.com",
port:587,
user_name: ENV['MAIL_USER_NAME'], #送信元となるメールアドレス(xxxx@gmail.com)
password: ENV['MAIL_PASSWORD'], #アプリパスワード
authentication: :login
}
...
end
addres
やport
の記述については、お約束のようなものになります。
なお、本番環境の場合には、config.action_mailer.default_url_options
の部分をドメイン名などにすればよいです。
また、user_name
やpassword
はセキュリティの観点から.env
ファイルに記述しておきましょう。
これでメールを送信する準備が完了しました。
【バッチ処理】
最初に記述したように、今回は毎日AM9:00に次の日のスケジュールをリマインドしたいので、定時的に処理を実行するようバッチ処理を実装します。
まずは、lib
フォルダの配下にbatch
フォルダを作成し、その中にremaind_event.rb
を作成してください。そして、どのような処理をするのかをファイルの中に記述しましょう。
class Batch::RemaindEvent
def self.remaind_event
# Event.reload
events = Event.all
events.each do |event|
time_difference = (event.start_at - Time.now.in_time_zone("Tokyo")) / 3600 # AM9:00との時間差を時間単位で算出する
if time_difference <= 39 && time_difference >= 15 && event.user.email_receiving_activation == true # 次の日のスケジュールなのかユーザーがリマインド機能をオンにしているかを判定
RemaindEventMailer.creation_email(event).deliver_now # メールを送信するためのメソッド
p "メールを#{event.user.name}に送信しました" # ログに表示するメッセージ
end
end
end
end
処理内容を細かく説明しますと、以下のようになります。
- Eventモデルに存在する全てのデータを取得してeachメソッドを実行
-
time_defference
にてプログラム実行時刻(AM9:00)とイベント開始時刻との時間差を時間単位で算出 - AM9:00から見て、イベントの開始時刻が15時間以上39時間以内であれば、次の日のイベントであるため、if文の条件に記述
- さらに、ユーザーがリマインド機能をオンにしていることも条件になるため、併せてif文に記述
-
RemaindEventMailer.creation_email(event).deliver_now
で即時的にメールを送信する - 実行が完了したら、ログにメッセージを表示させる
この時点で、処理自体は実行できるようになったので、ターミナルで以下のコマンドを実行すると、メールが送信されます。
bundle exec rails runner Batch::RemaindEvent.remaind_event
問題なく実行されていれば、ログのメールを#{event.user.name}に送信しました
が表示されるはずです。
次に、定時的に実行させるためのバッチ処理を実装します。こちらには、cron
という常駐プログラムを使用します。cron
については、こちらのサイトでの説明がわかりやすいかと思います。
そして、cronのスケジュール管理をするには、gemのwhenever
を使用します。Gemfileに追加してください。
gem 'whenever', require: false
インストールするとともに、スケジュールファイルを作成するために以下のコマンドを実行してください。
bundle install
bundle exec wheneverize
config/schedule.rb
が作成されるので、その中に実行するスケジュールを記述します。
require File.expand_path(File.dirname(__FILE__) + "/environment")
rails_env = Rails.env.to_sym
set :environment, rails_env
set :output, "log/cron.log" # ログをアウトプットする
every 1.day, at: '00:00' do # UTC時刻で00:00なので日本時刻09:00
begin
runner "Batch::RemaindEvent.remaind_event" # この処理を実行する
rescue => e
Rails.logger.error("aborted rails runner")
raise e
end
end
every 1.day, at: '00:00'
で、毎日1回、UTC時刻で0:00(日本時刻でAM9:00)に実行するという意味になります。他のパターンもあるので、wheneverのGitHubをご参照ください。
記述が完了したら、ターミナルで以下のコマンドを実行することでスケジュールが反映されます。
bundle exec whenever --update-crontab
最後に、cronを再起動させましょう。
sudo systemctl start crond
これで毎日AM9:00にリマインダーメールが送信されるようになります。
補足ですが、ちゃんとバッチ処理が実行されているかを確認するには、sudo tail -f log/cron.log
をターミナルで実行してください。-f
を付けているので、監視している状態になります。抜け出したい時は、Ctrl + C
を押してください。
さいごに
以上になります。
少々ややこしい部分もありますが、ひとつひとつの理解はそんなに難しくないと思います。
スケジューラー機能を実装するのであれば、リマインダー機能はあった方が良いと思いますので、どなたかのお役に立てれば嬉しいです!!