neutligenの日記

初心者が商用開発に挑む姿を綴ります。僕のような初心者の方に「こんな奴もいるんかー」と思って読んでもらい、コメントまで貰えたら幸いです。

開発のわだち(9):リマインダメールを実装

「リマインダメールを実装」

目標:heroku上のrailsアプリにリマインダメール機能を実装

目次:
1.ActionMailerでリマインダメールを作成
2.heroku Schedulerでメールの発送設定

  

久々のブログです。 “あるある”ですが、納期に追われて全然書けてませんでした。 (実は納期もこのリマインダメール機能だけは一週間遅れてしまいました。。。) しかし、HTTPリクエストに反応する機能しか作ってなかった自分にとっても、この機能は勉強になったので忘れる前に記載します。
そもそもHTTPリクエストが発生しないのにどうやって処理すんねん。。。から始まりましたが、いろいろな友人に助けて頂き、簡単なHeroku Schrdulerに辿りつきました。

  

1.ActionMailerでリマインダメールを作成

まずは、リマインダメールの仕様を決めます。

リマインダメールの仕様

送信時間:毎朝 午前8:30
送信内容:ToDoリストの送付
送信内容詳細:

送信対象ToDoリスト
 :送信時間以降24時間以内に設定されているToDoリスト
 :リマインダーメールのフラグがセットされているリスト
 (開始時刻が設定されているもの)

本当はここまでの過程も全てブログにしたかったんですが、なかなか。。。 もし続けて読んでくださってる方は「あ、ToDoリストの機能を付けたんだな!」と思ってくださいw 仕様の詳細はgithubアカウントをご覧くださいm( )m

本当はセットされた時間に(分刻みで)発送するリマインダメールを作りたかったんですが、cronの設定やバックグラウンド処理、メール処理専用サーバーの作成を考えるとハードルが高かったので断念(今後実装したい)。

まずはアクションメーラーをジェネレートします。

$ rails generate mailer "クラス名" "メソッド名"

今回は"クラス名"を"RemindMailer"、"メソッド名"を"remind"として生成しました。

まずは、生成したクラスとメソッドを定義します。

app/mailers/remind_mailer.rb

class RemindMailer < ApplicationMailer

  def remind(user)
    @user = User.find_by(id: user)
    @to_do_lists = @user.to_do_lists
    @todays_to_do_lists = @to_do_lists.select{|tdl| tdl.todays_todo? && tdl.reminder_mail }
    mail(to: @user.email, subject: "[#{Date.today}]のリマインド")
  end
end

remindメソッドは引数userをとります。Userモデルから引数に一致するIDのuserインスタンス変数の@userに入れる。 @userのToDoリストをToDoListモデルから全部取ってきてインスタンス変数の@to_do_listsに入れます。 @to_do_listsの中でreminder_mailtrueになってて且つ、処理時間以降24時間以内に開始時刻が設定されているものをインスタンス変数@todays_to_do_listsに入れます。
@userのメールアドレスにその日の日付をタイトルに載せて、発送するという内容です。

ちなみに、todays_todo?はToDoListモデルに以下のように設定してあります。

app/models/to_do_list.rb

...略

  def todays_todo?
    schedule_sta < Time.now.since(24.hours) && schedule_sta >= Time.zone.now
  end

...略

開始時間schedule_staが処理時間と比べて24時間以降より小さい値で、且つ、現在時刻Time.zone.nowよりも未来のもの、つまり処理時間以降24時間以内に開始時刻が設定されているもの、ということです。

メールの内容は以下に作りました。

app/views/remind_mailer

メールは(次で設定するschedulerでつまづいた時の原因切り分けのためにも)テスト環境でエラーなく動くか確認しておいた方がいいです。
メールのプレビューについては、rails tutorialに非常にわかりやすく解説されてます。

ここまでで「リマインダメール」自体の設定は終わりです。
次はこの処理を着火させる設定です。


2.heroku Schedulerでメールの発送設定

リマインダメールを動かすにはHTTPリクエストがなくとも意図したタイミングで、処理をスタートさせる必要があります。
その機能を担ってくれるのが、Heroku Schedulerです。設定した時間に指定したrakeタスクを実行してくれる賢いやつです。
但し、ベストエフォートです。許してやってください。あとdynoが24時間動くような立派なサービスだと確実に有料になります。
詳しくはオフィシャルを参照。

まずはheroku schedulerに動かしてもらうrakeタスクを作ります。 以下の感じです。

lib/tasks/"動かしたい処理".rake
今回は"動かしたい処理"をscheduler.rakeにしました。

desc "This task is called by the Heroku scheduler add-on"
task :reminder_mail => :environment do
    all_tdls = ToDoList.all
    rmd_tdls = all_tdls.select{|tdl| tdl.todays_todo? && tdl.reminder_mail }
    rmd_usrs = rmd_tdls.map{|rmd_tdl| rmd_tdl.user_id}
    users = rmd_usrs.uniq
    users.each do |user|
    RemindMailer.remind(user).deliver
  end
end

heorokuのオフィシャルに従った書き出しで始めてます。タスクの内容は全てのToDoListを変数all_tdlsに入れて、reminder_mailがtrueで処理時間から24時間以内に開始時間が設定されているものを引っ張り出してrmd_tdlsに入れます。
rmd_tdlsに紐づくuser.idを配列にしてusersに入れます。usersの重複を取り除いてそれぞれにremindメソッドを実行、メールを発送します。

このrakeタスクが実行されれば、リマインダメールが飛びますね。 (メールはHeroku addonのSendGridで発送しています。ここでは触れません。悪しからず。。)

そもそも「rakeってなんやねん」って方は、自分がrakeを学ばせてもらったこちらを見てみてください。
簡単に言うとrubyのプロビジョニングファイルみたいなものでしょうか(語弊があったらすみません)。

では、次にheroku schedulerが毎日午前8:30にこれを動かすように設定します。

heroku schedulerを該当のdynoに設定しましょう。

$ heroku addons:create scheduler:standard

rakeタスクがちゃんと動くか、確認します。

$ heroku run rake reminder_mail

実行するrakeタスクはscheduler.rakereminder_mailというタスクです。
rakeを実行するには$ rake "実行したいrakeタスク"という書き方になるので、上のように書きます。

ここまできたら、時間を設定するだけですね。

$ heroku addons:open scheduler

このコマンドで以下の画面がブラウザで立ち上がります。 実行したいrakeタスクを入力しましょう。 f:id:neutligen:20160427223002p:plain

"FREQUENCY"をDailyに"NEXT DUE"を8:30にセットしてます。
日本は UTC協定世界時」よりも9時間早く進んでいるので、設定したい日本時間から9時間引いた時間をセットする必要があります。

こうなると、当たり前ですがHeroku dynoの時間も日本時間にしなくてはなりません。

$ heroku config:add TZ=Asia/Tokyo
$ heroku config
# 設定されたタイムゾーンを確認できます。
…略
TZ:                       Asia/Tokyo

メールがちゃんと送れるか確認するには、"FREQUENCY"を10分おきとにしてやってみる手があります。

以上です。 heroku schedulerは便利ですが、rakeタスクを学ぶのが少し大変でしたー。

今回もっとも参考になった記事:How to send a daily report with Heroku's scheduler

環境

ホストOS: MacOSX 10.11.3
VirtualBox: 5.0.14
Vagrant: 1.7.4
ゲストOS: CentOS 7

ゲストOSの環境

Apache: 2.4.6
ruby: 2.2.1p85
Rails: 4.2.6