【Rails】ハッシュタグ機能を実装


概要

ハッシュタグ機能を実装する方法をまとめます。

参照

https://glodia.jp/blog/3936/
https://qiita.com/Naoki1126/items/4ea584a4c149d3ad5472

これら2つの記事の情報を組み合わせて実装しました。
ありがとうございます。

完成イメージ

今回は、写真投稿アプリを題材に、写真のキャプションにハッシュタグを導入する方法を説明します。

写真詳細画面のキャプションにリンク付きハッシュタグが記載され、クリックするとそのハッシュタグのページが表示される。

開発環境

  • macOS Catalina 10.15.7
  • ruby 2.6.5
  • Rails 6.0.3.4

実装の流れ

  • 各種モデルとマイグレーションファイルを準備
  • ハッシュタグ保存・更新アクションをモデルに追加
  • ルーティングを設定
  • ヘルパーメソッドを作成
  • コントローラーにhashtagアクションを作成
  • ビューの編集

今回のコード

コードは、必要な部分以外は省略して記載します。

1. 各種モデルとマイグレーションファイルを準備

hashtagモデルを作成

%rails g model hashtag

中間テーブルを作成

%rails g model photo_hashtag_relation

マイグレーションファイルを編集

テーブルのカラムを設定します。

create_hashtags.rb
class CreateHashtags < ActiveRecord::Migration[6.0]
  def change
    create_table :hashtags do |t|
      t.string :hashname
      t.timestamps
    end
    add_index :hashtags, :hashname, unique: true
  end
end
create_photo_hashtag_relations.rb
class CreatePhotoHashtagRelations < ActiveRecord::Migration[6.0]
  def change
    create_table :photo_hashtag_relations do |t|
      t.references :photo, index: true, foreign_key: true
      t.references :hashtag, index: true, foreign_key: true
      t.timestamps
    end
  end
end

モデルを編集

バリデーションとアソシエーションを設定します。

hashtag.rb
class Hashtag < ApplicationRecord
  validates :hashname, presence: true, length: { maximum:99}
  has_many :photo_hashtag_relations
  has_many :photos, through: :photo_hashtag_relations
end
photo_hashtag_relation.rb
class PhotoHashtagRelation < ApplicationRecord
  belongs_to :photo
  belongs_to :hashtag
  with_options presence: true do
    validates :photo_id
    validates :hashtag_id
  end
end

マイグレート

%rails db:migrate

2. ハッシュタグ保存・更新アクションをモデルに追加

photoモデルに以下のコードを追加します。
これで、写真投稿(create)、編集(update)時にハッシュタグがhashtagsテーブルに保存されます。

photo.rb
# 省略

  has_many :photo_hashtag_relations
  has_many :hashtags, through: :photo_hashtag_relations
.
.
# 省略
.
.
  #DBへのコミット直前に実施する
  after_create do
    photo = Photo.find_by(id: self.id)
    hashtags  = self.caption.scan(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/)
    photo.hashtags = []
    hashtags.uniq.map do |hashtag|
      #ハッシュタグは先頭の'#'を外した上で保存
      tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#'))
      photo.hashtags << tag
    end
  end

  before_update do 
    photo = Photo.find_by(id: self.id)
    photo.hashtags.clear
    hashtags = self.caption.scan(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/)
    hashtags.uniq.map do |hashtag|
      tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#'))
      photo.hashtags << tag
    end
  end

end

ルーティングを設定

photosコントローラーにhashtagアクションを定義し、getで各ハッシュタグのページを表示させます。
URLは末尾にハッシュタグ名が来るようにしました。
:nameに入る値はこの後ヘルパーメソッドで設定します。

例えば、#カメというハッシュタグだとURLは
.../photo/hashtag/カメ
となります。

route.rb
get '/photo/hashtag/:name', to: "photos#hashtag"

ヘルパーメソッドを作成

photos_helper.rbに以下のコードを記載します。
photos_helper.rbファイルがない場合は自分で作成します。

helperについては、https://www.sejuku.net/blog/28563 などを参照

このヘルパーメソッド使用により、リンク付きのハッシュタグが入ったキャプションが作成されます。
コード内では、ハッシュタグ名が末尾に入ったURLが作成され、ハッシュタグクリック時のリンク先として設定されています。
ハッシュタグ名の前のURLには、先程route.rbに書いたURLを書き込みます。

photos_helper.rb
module PhotosHelper
  def render_with_hashtags(caption)
    caption.gsub(/[##][\w\p{Han}ぁ-ヶヲ-゚ー]+/){|word| link_to word, "/photo/hashtag/#{word.delete("#")}"}.html_safe
  end 
end

コントローラーにhashtagアクションを作成

ハッシュタグに紐付いた写真を@photosに代入し、hashtagビューで使用し表示させます。

photos_controller.rb
  def hashtag
    @user = current_user
    @tag = Hashtag.find_by(hashname: params[:name])
    @photos = @tag.photos
  end

ビューの編集

先程作成したヘルパーメソッドrender_with_hashtagsを記載することにより、リンク付きハッシュタグが記載されたキャプションを写真詳細画面に表示させます。

show.html.erb
# 省略

<%= render_with_hashtags(@photo.caption) %>

ハッシュタグ画面では、hashtagアクションで設定した@photosを取り込み、ハッシュタグに紐づく写真を1枚ずつ表示させます。

hashtag.html.erb
<h2>ハッシュタグ #<%= @tag.hashname %></h2>
  <ul>
    <% @photos.each do |photo| %>
      <li>
        <%= link_to photo_path(photo.id) do %>
          <%= image_tag photo.image.variant(gravity: :center, resize:"640x640^", crop:"640x640+0+0"), if photo.image.attached? %>
        <% end %>
      </li>
    <% end %>
  </ul>

おわりに

以上が、今回行ったハッシュタグ機能実装の方法です。
意味が理解できていないコードが多々ありますが、追々理解していければと思います。

初学者なので間違いがありましたら、ご指摘いただきたいですm(_ _)m