🦓

[Rails]メンション

2023/09/18に公開

はじめに

正規表現の応用編としてコメントにユーザーのメンションを追加していきます。

環境

Rails 7.0.7
ruby 3.2.1

流れ

ユーザー、コメントなどを作成された前提で進めていきます。

  1. ユーザー名を正規表現でマッチする
  2. ユーザー名を抽出するヘルパーメソッドを作成する

ユーザー名を正規表現でマッチする

@usernameのように投稿やコメントなどのテキスト内からメンションされたユーザーを検出するために正規表現を使用します。
ユーザー名のフォーマットにバリデーションをかけた場合、それに沿って正規表現を設定します。
例えば、ユーザー名がロマ字、数字、ハイフンとアンダーバーで構成される場合、/@([a-zA-Z0-9_]+)/の正規表現になります。
今回はユーザー名に制限を設けてないため/@(\w+)/を使います。

irb(main):001:0> "@username @user user".match(/@(\w+)/)
=> #<MatchData "@username" 1:"username">

複数のユーザーがメンションされる場合、ユーザー名の配列を取得できると便利ですね。

irb(main):002:0> "@username @user user".scan(/@(\w+)/)
=> [["username"], ["user"]]
irb(main):003:0> "@username @user user".scan(/@(\w+)/).flatten
=> ["username", "user"]

バリデーション用正規表現集を参考にしてみてください。
https://gist.github.com/nashirox/38323d5b51063ede1d41
このサイトを正規表現のチェックにおすすめです!
https://rubular.com/

ユーザー名を抽出するヘルパーメソッドを作成する

コメントの作成や更新の直前にメンションがあるかどうかをチェックするためにヘルパーメソッドを作成します。

app/helpers/mentions_helper.rb
module MentionHelper
    def parse_at_mentions(comment_body)
      parsed_body = comment_body.dup
  
      comment_body.scan(/@(\w+)/).each do |mention|
        username = mention[0]
        user = User.find_by(name: username)
  
        if user
          replacement = view_context.link_to("@#{mention[0]}", user_path(user))
          parsed_body.gsub!("@#{mention[0]}", replacement)
        end
      end
  
      parsed_body
    end
end

parse_at_mentionsメソッドは、コメントのボディー内のメンション(例: @username)を検出し、それらをリンクに置き換えます。

.dupメソッドを使って、コメントのコピーを作成します。これは、parsed_bodyへの変更がオリジナルのコメントに影響を与えないようにするためです。

正規表現を使用しメンションの配列を取得します。
検出した各メンションに対して、@記号を取り除いたユーザー名をusername変数に格納します。

そして、User.find_byを使用して、ユーザー名(name)を指定して検索します。ユーザーが存在しない場合エラーを起こさないようにfind_byを使います。

指定されたユーザー名のユーザーが見つかった場合、view_context.link_toを使用して、置換用の文字列(replacement)を作成します。
view_contextはビューファイル以外のファイルででリンクを生成するヘルパーメソッドです。
これは、メンションに@マークとユーザー名を含むリンク(user_path(user))を生成します。

最後に、メンションを、gsub!を使用して replacement 文字列に置き換えます。これにより、メンションがクリック可能なリンクに置き換えられます。

https://api.rubyonrails.org/classes/ActionView/Rendering.html#method-i-view_context

コメントコントローラー内にヘルパーメソッドを呼び出す

新規作成と更新のタイミングでメンションがあるかどうかをチェエクします。

app/controllers/comments_controller.rb
@comment.body = parse_at_mentions(@comment.body)

ユーザーをメンションしてみます。
ユーザーが存在する場合、リンクを作成されることを確認します。
redcarpetgemを使っているため、リンクにCSSを適用されています。

終わりに

正規表現のパターンマッチでユーザーをメンションすることができました!
今回はメンションを試したかっただけなので簡単にまとめました。
メンションされたユーザーに通知を送ることができるともっと便利かもしれないですね。

app/models/comment.rb
def notifiy_mentioned_users
  mentioned_users = comment_body.scan(/@(\w+)/).flatten 
  mentioned_users.each do |user|
    next if user.id == comment.user.id
    Notification.create(
      sender_id: comment.user.id, 
      recipient_id: user.id, 
      notifiable: self)
  end
end

調べたらメンションのgemもありました。
https://github.com/komagata/mentionable

Discussion