はじめに
社内のハッカソンで(ほぼ)初めてActionMailerとActioveJobを使ってみました。
まだ自分の中で、何の機能を誰が担うのかの整理ができていないので、復習を兼ねて記事にしてみました!
※本番環境にデプロイしていません。
動作環境
ruby 3.3.1、Rails 7.1.3.3
読んで欲しい人
ActiveJob、ActionMailerの存在は知っていて、使ってみたいと思っている人。
実装すること
全体のざっくりした機能の流れとしては、ユーザが予定の外出時間の登録or更新をした時に、その外出時間の10分前に確認メールを飛ばすといったものです。
もしユーザがすでに予定の外出時間を登録していて、外出時間の更新をした場合、更新前の外出時間をもとに飛ばそうとしていたメールをキャンセルし、新たにメールの通知予約を作成します。
実装
※ UserモデルとUserに紐づくCalendarモデルは作成済みです。
予定の10分前に飛ばすメールを作成
まずはメールを作成します。
※今回は準備物を確認を促す通知なので、↓のような名前で作成しました。
rails g mailer Essentials encourage_confirmation
class EssentialsMailer < ApplicationMailer
default from: 'notifications@example.com'
def encourage_confirmation
@user = params[:user]
@calendar = params[:calendar]
mail to: @user.email, subject: '準備物を確認してください'
end
end
メールのテキスト内容は省略します。
メールを飛ばすjobを作成
先ほど作成したメールを送信する、jobを作成します。
rails g job send_check_essentials_mail
class SendCheckEssentialsMailJob < ApplicationJob
queue_as :default
def perform(user, calendar)
EssentialsMailer.with(user:, calendar:).encourage_confirmation.deliver_now
end
end
外出時間の10分前にjobを実行
ユーザが登録した外出時間の10分前に通知を飛ばすので、外出時間を管理するcalendarモデルのouting_atが更新された時に、先ほど作成したjobを実行するようにします。
※ calendar.rb作成時点ではouting_atはnilです
class Calendar < ApplicationRecord
belongs_to :user
after_update do
SendCheckEssentialsMailJob.set(wait_until: outing_at - 10.minutes).perform_later(user, self)
end
end
更新前の外出時間をもとに飛ばそうとしていたメールの予約をキャンセルし、新たにメールの通知予約をする
ここが少し難しかったところです。
まずは、外出時間を更新した時に通知を削除できるように、calendarとjobを紐付けます。
good_jobsテーブルのactive_job_idでjobを一意に識別できるみたいなのですが、モデルが存在しません。
なので、calendarにgood_job_idカラムを挿入し、不要になったjobをcalendarに登録されたgood_job_idをもとに、生のsql文を用い削除します。
class Calendar < ApplicationRecord
unless outing_at_before_last_save == outing_at
if good_job_id.present?
# jobが存在するのか確認
sql = "select active_job_id from good_jobs where active_job_id = uuid('#{good_job_id}')"
result = ActiveRecord::Base.connection.select_all(sql)
# 元の通知を削除する
if result.rows.present?
sql = "delete from good_jobs where active_job_id = uuid('#{good_job_id}')"
ActiveRecord::Base.connection.execute(sql)
end
end
job = SendCheckEssentialsMailJob.set(wait_until: outing_at - 10.minutes).perform_later(user, self)
# calendarのgood_job_idを新しく作成しjobのidに更新する
update!(good_job_id: job.job_id)
end
end
最後に
いかがだったでしょうか?
個人的に、good_jobのjobを削除する方法についての、good_jobに用意されていないことや参考になる記事が存在しなかったことが意外(探せなかっただけかも)でした。
稀なケースなんでしょうか?
それと、good_jobsテーブルがモデルであれば、calendarとhas_oneの関連付けができ、もっと実装がシンプルだったと思います。
もしテーブルをモデル化できるようであれば、シンプルな内容にして、もう一度挑戦してみたいと思います💪
ご意見、ご感想、アドバイス、どしどしお待ちしております!