1
0

【Rails】メンション付きコメントがあった際にメールで通知をする方法

Posted at

はじめに

Webアプリでよくある、@username でメンション通知をできる機能を実装したので、やり方を残しておきます。

学べたこと

  • after_createなどのコールバックを使う際のデータの扱い方
  • メールを送りたいときのメソッドの作り方
  • 絶対リンクの作り方

やりたいこと

ポストに対してのコメントで'@username'と記述した際にメンション通知を飛ばしたい。
メール送付は非同期処理で行いたい。

前提

  • Redisとsidekiqを導入

やり方

方向性

  1. メンション通知用のメソッドをMailerを作って実装
  2. viewの作成
  3. after_createを使って、Commentモデル内にメール送付のメソッドを定義
    1. 保存したコメントの内容から'@'に反応してusernameを検知
    2. もしメンションされていたら、メールを送信

Mailerの内容

class CommentsMailer < ApplicationMailer
  def mention_notification(mentioned_user, comment)
    @comment = comment
    @mentioned_user = mentioned_user
    @post_url = post_url(@comment.post)
    mail to: @mentioned_user.email, subject: '【お知らせ】メンションされました'
  end
end

ポイント

使われ方の想定

大前提からなのですが、このメソッドはモデルファイル(comment.rb)でafter_createの一部として、呼び出されることを想定しています。

引数について

引数は、メールを送るため+メール本文で使うために設定しています。
mentioned_user@usernameでメンションされたユーザーを指しています。
送付先のメールアドレスを特定するために必要です。

commentは主にcommentの内容と、commentがついたポストへのリンクを生成するため引数に指定しています

蛇足ですが、メソッドや関数を作るうえで引数に何を設定したら悩むことが多かったのですが、このようにどういったデータを扱う必要があるのか、というところから逆算して設定するものなのだとはじめて腑に落ちました。

post_urlメソッド

post_url(モデルのインスタンス)でインスタンスまでのurlを簡単に作成できるメソッドです。
めっちゃ便利。覚えておこう。

viewの内容

%p #{@mentioning_user.username}さんがあなたをメンションしました
= @comment.content

= link_to '該当のポストはこちらから確認できます', @post_url,  target: '_blank'

ポイント

内容について

メールを簡単に見ただけで必要な情報が載るように記述しました。
ポイントと言うほどでもないのですが。

Commentモデルの内容

class Comment < ApplicationRecord
  after_create :check_for_mentions

  private
  def check_for_mentions
    mentioned_usernames = content.scan(/@([^\s@]+)/).flatten
    mentioned_users = User.where(username: mentioned_usernames)
    mentioned_users.each do |mentioned_user|
      send_mention_notification(mentioned_user)
    end
  end

  def send_mention_notification(mentioned_user)
    CommentsMailer.mention_notification(mentioned_user, self).deliver_later
  end
end

ポイント

コメントの内容をどうやって扱うか

content.scan(/@([^\s@]+)/).flatten は省略形で本来はself.content.scan(/@([^\s@]+)/).flattenとなります。

この文脈だとselfは作成されたcomment(インスタンス)を指しています。
なのでcontent以外のカラムも取ろうと思えば取れます。

@以下がusernamなのかどう判定するか?

まずは、@以下を適切な単位で取得するため、正規表現を使います。
.scan(/@([^\s@]+)/)
正規表現はAIに任せてしまったのですが以下の意味になるようです

  • は否定
  • \sは空白
  • @はそのまま@

つまり空白や@ではないすべての文字をという意味になります。
なので、ユーザーが良心的で@usernameの後ろにスペースを入れてくれたら適切にメンションできますが、"@usernameいいね!"のように空白がないとうまく取得できなくなってしまいます。

これは今後の改善点です…

次に以下で取得した@以下の文字列がusernameなのかを確認しています。
User.where(username: mentioned_usernames)

whereを使っているのがポイントで、複数人へのメンションにも対応でき、@がメンションではない形で使われていた場合でもエラーが出ません。

どうやってメールを送るのか

取得したメンションされたユーザーの配列を以下で取り出して、一人ずつメールを送ります。

    mentioned_users.each do |mentioned_user|
      send_mention_notification(mentioned_user)
    end

send_mention_notificationは別途下記のように定義します。

  def send_mention_notification(mentioned_user)
    CommentsMailer.mention_notification(mentioned_user, self).deliver_later
  end

Mailerで作ったmention_noteificationに引数を渡しています。 また.deliver_later`とすることで非同期処理で送付しています。

これでコメントにメンションがあれば、自動でメールが送付されます!

おわりに

とりあえず実装はできましたが、メール送付までやたら時間がかかるので、別途リファクタリングを行おうと思います。
この辺のパフォーマンス改善については知見が0なので勉強しよー

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