1
0

予定の10分前にメール通知を飛ばす機能を作ってみた!

Last updated at Posted at 2024-07-08

はじめに

社内のハッカソンで(ほぼ)初めて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
app/mailers/essentials_mailer.rb
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
app/jobs/send_check_essentials_mail_job.rb
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です

app/models/calendar.rb
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文を用い削除します。

app/models/calendar.rb
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の関連付けができ、もっと実装がシンプルだったと思います。
もしテーブルをモデル化できるようであれば、シンプルな内容にして、もう一度挑戦してみたいと思います💪
ご意見、ご感想、アドバイス、どしどしお待ちしております!

1
0
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
1
0