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



















# 概要

備忘録としてインスタやTwitterでよく見かける#ハッシュタグの実装方法を投稿します


## 参照

以下の記事を参考にしました。
ほとんど同じですが、上手く行かなかったところを自分なりに組み合わせました。
https://qiita.com/Naoki1126/items/4ea584a4c149d3ad5472
https://qiita.com/MitsuTurtle/items/614b154641ab4e4b56ba
https://glodia.jp/blog/3936/

# 完成図
![hashtag_video2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2287171/6e4391e2-9766-8462-470b-f006065e6f9c.gif)



# アプリの作成
```ruby:ターミナル
$cd desktop
$rails new hashtag_sample
$cd hashtag_sample
```
# DBの作成
DB（データベース）を作成していない人は作成しましょう！
作成済みの人は無視して次のモデルの作成に進みましょう！

```ruby:ターミナル
$rails db:create
```
#モデルの作成
```ruby:ターミナル
$rails g model Post body:text hashbody:text
$rails g model Hashtag hashname:string
$rails g model HashtagPost post:references hashtag:references #中間テーブル
```

# コントローラーの作成
```ruby:ターミナル
$rails g controller posts
```

## マイグレーションファイルの編集
```ruby:db/migrate/create_hashtags.rb
class CreateHashtags < ActiveRecord::Migration[6.1]
  def change
    create_table :hashtags do |t|
      t.string :hashname

      t.timestamps
    end
    add_index :hashtags, :hashname, unique: true
  end
end
```

ここでマイグレーション

```ruby:ターミナル
$rails db:migrate
```

# モデルのアソシエーションを記述
```ruby:model/hashtag.rb
class Hashtag < ApplicationRecord
    validates :hashname, presence: true, length: { maximum: 50 }
    has_many :hashtag_posts, dependent: :destroy
    has_many :posts, through: :hashtag_posts
end
```
```ruby:model/hashtag_post.rb
class HashtagPost < ApplicationRecord
  belongs_to :post
  belongs_to :hashtag
  validates :post_id, presence: true
  validates :hashtag_id, presence: true
end
```


```ruby:model/post.rb
class Post < ApplicationRecord
    has_many :hashtag_posts, dependent: :destroy
    has_many :hashtags, through: :hashtag_posts

    after_create do
        post = Post.find_by(id: id)
        # hashbodyに打ち込まれたハッシュタグを検出
        hashtags = hashbody.scan(/[#＃][\w\p{Han}ぁ-ヶｦ-ﾟー]+/)
        hashtags.uniq.map do |hashtag|
            # ハッシュタグは先頭の#を外した上で保存
            tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#'))
            post.hashtags << tag
        end
    end
        #更新アクション
    before_update do
        post = Post.find_by(id: id)
        post.hashtags.clear
        hashtags = hashbody.scan(/[#＃][\w\p{Han}ぁ-ヶｦ-ﾟー]+/)
        hashtags.uniq.map do |hashtag|
            tag = Hashtag.find_or_create_by(hashname: hashtag.downcase.delete('#'))
            post.hashtags << tag
        end
    end
 end

```

# ルーティングについて

```ruby:config/routes.rrb
Rails.application.routes.draw do
  resources :posts
  root'posts#index'
  get '/post/hashtag/:name' => 'posts#hashtag'
  get '/post/hashtag' => 'posts#hashtag'
end
```

# ヘルパーの編集

```ruby:helpers/posts_helper.rb
module PostsHelper
    def render_with_hashtags(hashbody)
        hashbody.gsub(/[#＃][\w\p{Han}ぁ-ヶｦ-ﾟー]+/) { |word| link_to word, "/post/hashtag/#{word.delete("#")}",data: {"turbolinks" => false} }.html_safe
    end
end
```

# post_controller
```ruby:controllers/post_controller.rb
class PostsController < ApplicationController

  def hashtag
    if params[:name].nil?
      @hashtags = Hashtag.all.to_a.group_by{ |hashtag| hashtag.posts.count}
    else
      name = params[:name]
      name = name.downcase
      @hashtag = Hashtag.find_by(hashname: name)
      @post = @hashtag.posts
      @hashtags = Hashtag.all.to_a.group_by{ |hashtag| hashtag.posts.count}
    end
  end


  def index
    @posts = Post.all
  end

  def show
    @post = Post.find(params[:id])
  end

  def new
    @postnew = Post.new
  end

  def create
    @postnew = Post.new(post_params)

    if @postnew.save
      redirect_to posts_path
    else
      render('posts/new')
    end
  end

  def update
    @post = Post.find(params[:id])
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to @post, notice: "Post  was successfully updated." }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

  def destroy
    @post = Post.find(params[:id])
    @post = Post.find(params[:id])
    @post.destroy
    redirect_to posts_path
  end


  private

    def post_params
      params.require(:post).permit(:body, :hashbody, :user_id, postss: [], hashtag_ids: [])
    end
end
```

# Viewの編集
まずはapp/view/postsに
・index.html.erb
・new.html.erb
・hashtag.html.erb
・show.html.erbを作成！

### 投稿フォーム
```ruby:views/posts/new.html.erb
<div class= "row">
    <div class="col-lg-2 col-md-2">
    </div>
    <div class="col-xs-12 col-lg-8 col-md-8 col-sm-12">
        <div class= "post-new-box">
            <% if @postnew.errors.any? %>
                <div id="error_explanation">
                    <h3><%= @postnew.errors.count %>件の入力エラーにより、投稿出来ませんでした</h3>
                    <ul>
                        <% @postnew.errors.full_messages.each do |msg| %>
                        <li><%= msg %></li>
                        <% end %>
                    </ul>
                </div>
            <% end %>
            <h3>新規投稿</h3>
            <div class="previw">
            </div>
            <%= form_for @postnew, url: posts_path do |f| %>
                <br>
                <div class= "post-body-box">
                    <p>投稿の詳細を入力してください</p>
                    <%= f.text_area :body, size:"55x3" %>
                    <br>
                    <p>ハッシュタグ入力欄</p>
                    <%= f.text_area :hashbody, size:"55x3" %>
                    <br>
                    <div class= "post-new">
                    <%= f.submit "新規投稿" ,class:'post-new-button' %>
                    </div>
            <% end %>
                </div>
        </div>
    </div>
    <div class="col-lg-2 col-md-2">
    </div>
</div>


<%= link_to 'Back', posts_path %>

```
### 詳細ページ
```ruby:views/posts/show.html.erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Body:</strong>
  <%= @post.body %>
</p>

<p>
  <strong>Hashbody:</strong>
  <%= render_with_hashtags(@post.hashbody) %>
</p>

<%= link_to 'Back', posts_path %>
```


### ハッシュタグ
```ruby:views/posts/hashtag.html.erb
<h2>ハッシュタグ #<%= @hashtag.hashname %></h2>
  <div class= "hashtag-name">
    <% @hashtags.sort.reverse.each do |count| %>
        <% count[1].each do |hashtag| %>
            <p><%= link_to  "##{hashtag.hashname} (#{hashtag.posts.count}) 件","/post/hashtag/#{hashtag.hashname}",data: {"turbolinks" => false} %>
            </p>
        <% end %>
    <% end %>
  </div>
    <ul>
        <% @post.each do |t| %>
        <li>
            <%= t.body %>
        </li>    
        <% end %>
    </ul>
    
<%= link_to 'Back', posts_path %>
```

### 投稿一覧
```ruby:views/posts/index.html.erb
   <p id="notice"><%= notice %></p>

<h1>投稿一覧</h1>

<table>
  <thead>
    <tr>
      <th>Body</th>
      <th>Hashbody</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.body %></td>
        <td><%= render_with_hashtags(post.hashbody) %></td>
        <td><%= link_to 'Show', post %></td>
        <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Post', new_post_path %>
```
完成！
お疲れ様でした！