0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails初学者】備忘録:通知機能の実装メモ - Sidekiq + ActionMailerでの即時・スケジュール通知

Posted at

本記事は、自分のメモです

通知機能実装メモ - Sidekiq + ActionMailerでの即時・スケジュール通知

はじめに

アプリで「記事(Post)公開時の即時通知」と「翌日のリマインド通知」を実装した際の備忘録です。
Sidekiq + ActionMailerを使用した非同期処理による通知機能の実装を忘れないために残します。

構成

  • 即時通知: Post公開時にすぐに送信
  • リマインド通知: 公開日の翌日に送信(スケジューリング)
  • 技術スタック: Rails 8, Sidekiq, ActionMailer, letter_opener(開発環境)

モデル構成

Notificationモデル

class Notification < ApplicationRecord
  belongs_to :user
  belongs_to :post

  validates :title, :description, :due_date, presence: true
end

Postモデルの通知作成処理

class Post < ApplicationRecord
  belongs_to :user

  # 通知を作成するメソッド
  def create_notifications
    # 1. 即時通知を作成
    immediate_notification = user.notifications.create!(
      title: "【新しい記事を公開しました】#{title}",
      description: "タイトル: #{title}\n公開日: #{published_at.strftime('%-m月%-d日 %H:%M')}\n概要:#{summary}",
      due_date: Date.current,
      post: self
    )

    # 2. 即時通知をジョブキューに送信
    ImmediateNotificationJob.perform_later(immediate_notification.id)

    # 3. 翌日リマインド通知を作成(未送信状態で保存)
    user.notifications.create!(
      title: "【記事リマインド】#{title}",
      description: "昨日公開した記事をご覧になりましたか?\nタイトル: #{title}\n公開日: #{published_at.strftime('%-m月%-d日 %H:%M')}",
      due_date: published_at.to_date + 1.day,
      post: self
    )
  end
end

ジョブクラス

即時通知ジョブ

class ImmediateNotificationJob < ApplicationJob
  queue_as :default

  def perform(notification_id)
    notification = Notification.find(notification_id)
    
    Notification.transaction do
      NotificationMailer.immediate_notification(notification).deliver_now!
      notification.update!(is_sent: true)
    end
  rescue => e
    Rails.logger.error "ImmediateNotificationJob failed: #{e.message}"
    raise
  end
end

翌日リマインドジョブ

class PostReminderJob < ApplicationJob
  queue_as :default

  def perform
    target_date = Time.zone.tomorrow

    Notification.includes(:post)
                .where(is_sent: false, due_date: target_date)
                .find_each do |notification|
      Notification.transaction do
        NotificationMailer.post_reminder(notification).deliver_now!
        notification.update!(is_sent: true)
      end
    end
  end
end

コントローラ処理

PostsController

class PostsController < ApplicationController
  def create
    @post = current_user.posts.build(post_params)
    if @post.save
      @post.create_notifications # モデルのメソッドを呼ぶ

      redirect_to posts_path, notice: "記事を公開しました"
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def post_params
    params.require(:post).permit(:title, :content, :summary, :published_at)
  end
end

動作確認方法

1. 開発環境での確認

Railsコンソールでの手動テスト

# 1. Postを作成
user = User.first
post = user.posts.create!(
  title: "Railsで通知システムを作る",
  content: "本文...",
  summary: "Rails + Sidekiqで通知システムを構築した話",
  published_at: Time.zone.now
)

# 2. 通知を作成(コントローラ経由でなく直接呼び出し)
post.create_notifications

# 3. 即時通知の確認
Notification.where(post: post, is_sent: true).first.title
# => "【新しい記事を公開しました】Railsで通知システムを作る"

# 4. 翌日リマインド通知の確認
Notification.where(post: post, is_sent: false).first.title
# => "【記事リマインド】Railsで通知システムを作る"

2. 翌日リマインド通知のテスト

# テスト用に通知のdue_dateを変更
n = Notification.find(10) # リマインド通知のID
n.update!(due_date: Time.zone.tomorrow)

# ジョブを実行
PostReminderJob.perform_now

# 送信済みに更新されているか確認
n.reload.is_sent  # => true

重要なポイント

  1. コールバックは使わず明示的に呼び出す

    • コントローラで create_notifications を呼ぶことで責務を分離。
  2. includes(:post) でN+1回避

    • リマインド処理時に関連データを効率的に取得。
  3. トランザクションで整合性確保

    • メール送信と通知状態更新を一括管理。

実際のログ例

Post Create INSERT INTO "posts" ...

Notification Create '【新しい記事を公開しました】Railsで通知システムを作る'

Enqueued ImmediateNotificationJob (Job ID: ...) to Sidekiq(default)

Notification Create '【記事リマインド】Railsで通知システムを作る'

Notification Update SET "is_sent" = TRUE WHERE "notifications"."id" = 10

まとめ

  • 即時通知: 記事公開と同時にユーザーへ通知
  • スケジュール通知: 翌日にリマインドを送信
  • 非同期処理: Sidekiqでレスポンス性能を維持
  • データ整合性: トランザクションで通知状態を保証

本番環境では sidekiq-cronwhenever を使って PostReminderJob を毎日実行することで、自動的なリマインド配信が可能になる。

感想

勉強になりましたが、RailsのJob関連は、本当に難しいと実感しました。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?