はじめに
Webアプリでよくある、@username でメンション通知をできる機能を実装したので、やり方を残しておきます。
学べたこと
-
after_create
などのコールバックを使う際のデータの扱い方 - メールを送りたいときのメソッドの作り方
- 絶対リンクの作り方
やりたいこと
ポストに対してのコメントで'@username'と記述した際にメンション通知を飛ばしたい。
メール送付は非同期処理で行いたい。
前提
- Redisとsidekiqを導入
やり方
方向性
- メンション通知用のメソッドをMailerを作って実装
- viewの作成
-
after_create
を使って、Commentモデル内にメール送付のメソッドを定義- 保存したコメントの内容から'@'に反応してusernameを検知
- もしメンションされていたら、メールを送信
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なので勉強しよー