複数チェックボックス付き投稿・一覧表示・検索機能【RubyOnRails】

## 目標
- __投稿する際に事前に用意した選択肢からチェックボックスを生成、チェックをつけさせ、その情報をデータベースに保存する。__
- __投稿を一覧表示した際にチェックを付けた項目も見れるようにする。__
- __投稿する際に付けたチェックをもとに投稿を検索することができる。__

## 環境
- Ruby 3.0.4
- Rails 6.1.7

## 前提
- 投稿機能実装済み。
- 今回は関係ないですが、User機能、いいね機能も実装済みなので、そこら辺の記述は読み飛ばしてください。

## 完成形
##### コントローラー(投稿を扱うもの：今回はtweet_controller.rb)
```ruby:tweet_controller.rb
class TweetsController < ApplicationController
  before_action :authenticate_user!

  @@selections = ["てつや","しばゆー","ゆめまる","りょう","むしめがね","としみつ"]

#中略

  def index
      @tweets= Tweet.all
  end

  def search
    @selections = @@selections
    @tweets = []
    search_checkboxes = []

    for num in 0..@selections.length - 1 do
      search_checkboxes_index  = "search_checkbox_" + num.to_s
      if !params[search_checkboxes_index].nil?
        search_checkboxes.push(params[search_checkboxes_index])
      end
    end
    
    if params[:search_word] == ''and search_checkboxes == []
      @tweets= Tweet.all
    elsif params[:search_word] == nil and search_checkboxes == []
      @tweets= Tweet.all
    else
      tweets = Tweet.where("body LIKE ? ",'%' + params[:search_word] + '%')
      tweets.each do |d|
        union_checkbox = (d.checkboxes +  search_checkboxes).uniq
        if union_checkbox == d.checkboxes
          @tweets.push(d) 
        end
      end
    end

  end
  
  def new
    @tweet = Tweet.new
    @selections = @@selections
  end

  def create
    tweet = Tweet.new(tweet_params)
    tweet.user_id = current_user.id
    if tweet.save
      redirect_to :action => "index"
    else
      redirect_to :action => "new"
    end
  end

#中略

  private
  def tweet_params
    params.require(:tweet).permit(:body, :image,checkboxes:[])
  end

#後略
```
##### ルーティング
```ruby:routes.rb
#前略
  get 'tweet/search' => 'tweets#search'
#後略
```
##### モデル
```ruby:tweet.rb
#前略
    serialize :checkboxes, Array
#後略
```
##### ビューファイル
```erb:new.html.erb
<%= stylesheet_link_tag 'new', :media => "all" %>

<div class="post-container">
    <p class="title">投稿フォーム</p>
    <%= form_for(@tweet, :url => { controller:'tweets', action:'create'})do |f| %>
      <%= f.label :投稿内容 %><br>
      <%= f.text_field :body,size: 140%>

      <div class="field">
        <%= f.label :image %>
        <%= f.file_field :image %>
      </div>
      
      <div class="field">
        <% @selections.each_with_index do |checkboxes,i| %>
          <label>
            <%= check_box_tag 'tweet[checkboxes][]', checkboxes,false%>
            <%= checkboxes %>
          </label>
        <% end %>
      </div>

      <%= f.submit "送信"%><br>
    <% end %>
</div>

```


```erb:index.html.erb
<%= link_to "投稿を検索", tweet_search_path %>　

<div class="tweets-container">

<h3>Tweet一覧</h3>
  <% @tweets.each do |t| %>
    <div class="tweet">
      <div class="main-box">
        <div class="left-container">
          <%= t.user.email %>
          <a href="/users/<%= t.user.id %>"><%= t.user.name %></a>
          <%= t.body %>
          <%= image_tag t.image_url, size: "250x200" if t.image? %>
          <br>
          <%= t.checkboxes.join(' ')%>
        </div>

        <div class="right-container">
          <%= link_to "詳細", tweet_path(t.id) %>
          <% if user_signed_in? && current_user.id == t.user_id %>
          　<%= link_to "編集する", edit_tweet_path(t.id) %>
          　<%= link_to "削除する", tweet_path(t.id), method: :delete %>
      　　 <% end %>
        </div>
      </div>
      <% if user_signed_in? %>
        <% if current_user.already_liked?(t) %>
            <%= link_to tweet_like_path(id: t.id, tweet_id: t.id), method: :delete do %>
              <i class="fas fa-heart"></i><%= t.likes.count %>
            <% end %>
        <% else %>
            <%= link_to tweet_likes_path(id: t.id, tweet_id: t.id), method: :post do %>
              <i class="far fa-heart"></i><%= t.likes.count %>
            <% end %>
        <% end %>
      <% else %>
          <p>いいねの数 = </p><%= t.likes.count %>
      <% end %>
        <p class="time"><%= t.created_at %></p>
    </div>
  <% end %>
</div>


```

```erb:search.html.erb
<h3>投稿を検索</h3>
  <%= form_tag({controller:"tweets",action:"search"}, method: :get) do %>
    <%= text_field_tag :search_word %>
    <div class="field">
      <% @selections.each_with_index do |item,i| %>
      <% pram = "search_checkbox_" + i.to_s %>
          <label>
            <%= check_box_tag pram, item, false%>
            <%= item %>
          </label>
        <% end %>
      </div>
    <%= submit_tag '検索する'  %>
<% end %>

<h3>検索結果</h3>
  <% @tweets.each do |t| %>
    <div class="tweet">
      <div class="main-box">
        <div class="left-container">
          <%= t.user.email %>
          <a href="/users/<%= t.user.id %>"><%= t.user.name %></a>
          <%= t.body %>
          <%= image_tag t.image_url, size: "250x200" if t.image? %>
          <br>
          <%= t.checkboxes.join(' ')%>
        </div>

        <div class="right-container">
          <%= link_to "詳細", tweet_path(t.id) %>
          <% if user_signed_in? && current_user.id == t.user_id %>
            <%= link_to "編集する", edit_tweet_path(t.id) %>
            <%= link_to "削除する", tweet_path(t.id), method: :delete %>
          <% end %>
        </div>
      </div>
      <% if user_signed_in? %>
        <% if current_user.already_liked?(t) %>
            <%= link_to tweet_like_path(id: t.id, tweet_id: t.id), method: :delete do %>
              <i class="fas fa-heart"></i><%= t.likes.count %>
            <% end %>
        <% else %>
            <%= link_to tweet_likes_path(id: t.id, tweet_id: t.id), method: :post do %>
              <i class="far fa-heart"></i><%= t.likes.count %>
            <% end %>
        <% end %>
      <% else %>
          <p>いいねの数 = </p><%= t.likes.count %>
      <% end %>
        <p class="time"><%= t.created_at %></p>
    </div>
  <% end %>
</div>
```

## 手順
### 1.カラムの追加
はじめに投稿のテーブル(今回はTweet)にカラムを追加します。名前は何でもいい（今回はcheckboxes）ですがデータ型は`string`で用意してください。
```コマンドプロンプト/ターミナル
rails generate migration AddCheckboxesToTweets checkboxes:string
rails db:migrate
```
カラムの追加に関しては[この記事](https://qiita.com/azusanakano/items/a2847e4e582b9a627e3a)を参考にしてみてください。
railsのデータベースで配列を保存するためにシリアライズします。
```ruby:tweet.rb
#前略
    serialize :checkboxes, Array
#後略
```

### 2.checkbox付投稿の実装
`check_box_tag`を使ってチェックボックス機能を実装しました。複数あるので`each_with_index`メソッドを使用しています。
```erb:new.html.erb
<div class="post-container">
    <p class="title">投稿フォーム</p>
    <%= form_for(@tweet, :url => { controller:'tweets', action:'create'})do |f| %>
      <%= f.label :投稿内容 %><br>
      <%= f.text_field :body,size: 140%>

      <div class="field">
        <%= f.label :image %>
        <%= f.file_field :image %>
      </div>
      
      <div class="field">
        <% @selections.each_with_index do |checkboxes,i| %>
          <label>
            <%= check_box_tag 'tweet[checkboxes][]', checkboxes,false%>
            <%= checkboxes %>
          </label>
        <% end %>
      </div>

      <%= f.submit "送信"%><br>
    <% end %>
</div>

```

選択肢に関しては、投稿ページや検索ページで複数回書くのは面倒なので、冒頭にクラス変数で定義して再利用できる形にしました。ストロングパラメーターは配列の場合書き方が違うので注意してください。
```ruby:tweet_controller.rb
class TweetsController < ApplicationController
  before_action :authenticate_user!

  @@selections = ["てつや","しばゆー","ゆめまる","りょう","むしめがね","としみつ"]

#中略
  
  def new
    @tweet = Tweet.new
    @selections = @@selections
  end

  def create
    tweet = Tweet.new(tweet_params)
    tweet.user_id = current_user.id
    if tweet.save
      redirect_to :action => "index"
    else
      redirect_to :action => "new"
    end
  end

#中略

  private
  def tweet_params
    params.require(:tweet).permit(:body, :image,checkboxes:[])
  end

#後略
```

### 3.checkbox付一覧ページの実装
特に難しいことはしていません。`each do`を使って`checkboxes`の中身を表示しようとしましたが思ったようにいかなかったため、`join`メソッドを用いて間に半角スペースを入れた結合文字列にして表示しています。
```erb:index.html.erb
<%= link_to "投稿を検索", tweet_search_path %>　

<div class="tweets-container">

<h3>Tweet一覧</h3>
  <% @tweets.each do |t| %>
    <div class="tweet">
      <div class="main-box">
        <div class="left-container">
          <%= t.user.email %>
          <a href="/users/<%= t.user.id %>"><%= t.user.name %></a>
          <%= t.body %>
          <%= image_tag t.image_url, size: "250x200" if t.image? %>
          <br>
          <%= t.checkboxes.join(' ')%>
        </div>

        <div class="right-container">
          <%= link_to "詳細", tweet_path(t.id) %>
          <% if user_signed_in? && current_user.id == t.user_id %>
          　<%= link_to "編集する", edit_tweet_path(t.id) %>
          　<%= link_to "削除する", tweet_path(t.id), method: :delete %>
      　　 <% end %>
        </div>
      </div>
      <% if user_signed_in? %>
        <% if current_user.already_liked?(t) %>
            <%= link_to tweet_like_path(id: t.id, tweet_id: t.id), method: :delete do %>
              <i class="fas fa-heart"></i><%= t.likes.count %>
            <% end %>
        <% else %>
            <%= link_to tweet_likes_path(id: t.id, tweet_id: t.id), method: :post do %>
              <i class="far fa-heart"></i><%= t.likes.count %>
            <% end %>
        <% end %>
      <% else %>
          <p>いいねの数 = </p><%= t.likes.count %>
      <% end %>
        <p class="time"><%= t.created_at %></p>
    </div>
  <% end %>
</div>


```
```ruby:tweet_controller.rb

#ここから追記

  def index
      @tweets= Tweet.all
  end

#ここまで追記
```
### 4.checkbox付検索ページの実装
ここが今回の山場です。
    今回は検索ページと検索結果ページを同じにしているので、`<%= form_tag({controller:"tweets",action:"search"}, method: :get) do %>`として、ビューファイルから`Tweets`コントローラーの`search`アクションに`get`を飛ばしています。
まずはbodyカラム（主な内容を記述するカラム）であいまい検索をかけ、変数`tweets`に入れます。そしてtweetを対象に`each do`を行い、checkboxの検索条件を満たしたものをインスタンス変数として用意しておいた配列`@tweets`に格納していきます。
```ruby:tweet_controller.rb

#ここから追記

  def search
    @selections = @@selections
    @tweets = []
    search_checkboxes = []

    for num in 0..@selections.length - 1 do
      search_checkboxes_index  = "search_checkbox_" + num.to_s
      if !params[search_checkboxes_index].nil?
        search_checkboxes.push(params[search_checkboxes_index])
      end
    end
    
    if params[:search_word] == ''and search_checkboxes == []
      @tweets= Tweet.all
    elsif params[:search_word] == nil and search_checkboxes == []
      @tweets= Tweet.all
    else
      tweets = Tweet.where("body LIKE ? ",'%' + params[:search_word] + '%')
      tweets.each do |d|
        union_checkbox = (d.checkboxes +  search_checkboxes).uniq
        if union_checkbox == d.checkboxes
          @tweets.push(d) 
        end
      end
    end

  end

#ここまで追記

```
```erb:search.html.erb
<h3>投稿を検索</h3>
  <%= form_tag({controller:"tweets",action:"search"}, method: :get) do %>
    <%= text_field_tag :search_word %>
    <div class="field">
      <% @selections.each_with_index do |item,i| %>
      <% pram = "search_checkbox_" + i.to_s %>
          <label>
            <%= check_box_tag pram, item, false%>
            <%= item %>
          </label>
        <% end %>
      </div>
    <%= submit_tag '検索する'  %>
<% end %>

<h3>検索結果</h3>
  <% @tweets.each do |t| %>
    <div class="tweet">
      <div class="main-box">
        <div class="left-container">
          <%= t.user.email %>
          <a href="/users/<%= t.user.id %>"><%= t.user.name %></a>
          <%= t.body %>
          <%= image_tag t.image_url, size: "250x200" if t.image? %>
          <br>
          <%= t.checkboxes.join(' ')%>
        </div>

        <div class="right-container">
          <%= link_to "詳細", tweet_path(t.id) %>
          <% if user_signed_in? && current_user.id == t.user_id %>
            <%= link_to "編集する", edit_tweet_path(t.id) %>
            <%= link_to "削除する", tweet_path(t.id), method: :delete %>
          <% end %>
        </div>
      </div>
      <% if user_signed_in? %>
        <% if current_user.already_liked?(t) %>
            <%= link_to tweet_like_path(id: t.id, tweet_id: t.id), method: :delete do %>
              <i class="fas fa-heart"></i><%= t.likes.count %>
            <% end %>
        <% else %>
            <%= link_to tweet_likes_path(id: t.id, tweet_id: t.id), method: :post do %>
              <i class="far fa-heart"></i><%= t.likes.count %>
            <% end %>
        <% end %>
      <% else %>
          <p>いいねの数 = </p><%= t.likes.count %>
      <% end %>
        <p class="time"><%= t.created_at %></p>
    </div>
  <% end %>
</div>
```
```ruby:routes.rb
#前略
  get 'tweet/search' => 'tweets#search'
#後略
```