Railsでラベル機能（タグ付け）を実装する

# はじめに
RailsでTo Doアプリにラベル機能を実装しました。
今回はラベル機能の実装方法を紹介します。

前提として、私の環境は下記の通りです。

```ruby
Ruby 2.6.3

Rails 5.2.3
```

# 目的
- タスク作成時・更新時に、事前に作成したラベルを複数付けられるようにする。
- タスクに関連付けられているラベルを表示できるようにする。
- タスクをラベルで絞り込みできるようにする。

# 実装方法
それでは実装していきましょう。
まずTaskモデル・コントローラー・ビューをscaffoldで一括生成します。

```ruby
rails generate scaffold task title:string
```
　
次にLabelもscaffoldで一括生成してしまいましょう。

```ruby
rails generate scaffold label name:string
```
　
タスクにラベルを付与するためには、TaskとLabelを関連付けする必要があります。
今回は多対多の関連付けを行うので、結合モデル・中間テーブル（交差テーブル）を作りましょう。

中間テーブルの作り方はこちらのドキュメントが詳しいです。
- [Active Record の関連付け (アソシエーション) - Rails ガイド](https://railsguides.jp/association_basics.html#has-many-through%E9%96%A2%E9%80%A3%E4%BB%98%E3%81%91)

```ruby
rails generate model labelling task:references label:references

rails db:migrate
```
　
この時点でテーブル構造は以下のようになっていると思います。

```ruby:db/schema/db
ActiveRecord::Schema.define(version: 2019_06_29_021027) do

  create_table "labellings", force: :cascade do |t|
    t.integer "task_id", null: false
    t.integer "label_id", null: false
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["label_id"], name: "index_labellings_on_label_id"
    t.index ["task_id"], name: "index_labellings_on_task_id"
  end

  create_table "labels", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  create_table "tasks", force: :cascade do |t|
    t.string "title"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
  end

  add_foreign_key "labellings", "labels"
  add_foreign_key "labellings", "tasks"
end
```
　　
TaskモデルとLabelモデルに`has_many :through`を使って、多対多の関連付けを指定します。

```ruby:app/models/task.rb
class Task < ApplicationRecord
  has_many :labellings, dependent: :destroy
  has_many :labels, through: :labellings
end
```

```ruby:app/models/label.rb
class Label < ApplicationRecord
  has_many :labellings, dependent: :destroy
  has_many :tasks, through: :labellings
end
```

Labbelingモデルはこのようになっていると思います。

```ruby:app/models/labelling.rb
class Labelling < ApplicationRecord
  belongs_to :task
  belongs_to :label
end
```
　
## タスク作成・更新時にラベルを関連付けできるようにする

`app/views/tasks/_form.html.erb`のform_withヘルパーの中に、ラベルを選択できるチェックボックスを作ります。

```erb
<%= form.collection_check_boxes(:label_ids, Label.all, :id, :name) %>
```

form全体は例えばこのようになります。

```erb:app/views/tasks/_form.html.erb
<%= form_with(model: task, local: true) do |form| %>
  <% if task.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(task.errors.count, "error") %> prohibited this task from being saved:</h2>

      <ul>
      <% task.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div class="field">
    <%= form.collection_check_boxes(:label_ids, Label.all, :id, :name) %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
```
　
次にチェックボックスで選択したラベルを`tasks_controller.rb`のStrong Parametersで受け取れるようにしましょう。
ラベルを配列で送っているので、書き方が少し複雑になります。

```ruby:app/controllers/tasks_controller.rb
private

def task_params
  params.require(:task).permit(:title, { label_ids: [] })
end
```
これでタスク作成・更新時にラベルを関連付けすることができるようになりました。
※事前にラベルを作成しないと、ラベルがチェックボックスに表示されません。


## タスクに関連付けられているラベルを表示できるようにする
タスク一覧画面に実装します。

```erb:app/views/tasks/index.html.erb
<% @tasks.each do |task| %>
  <tr>
    <td><%= task.title %></td>
    <td><%= link_to 'Show', task %></td>
    <td><%= link_to 'Edit', edit_task_path(task) %></td>
    <td><%= link_to 'Destroy', task, method: :delete, data: { confirm: 'Are you sure?' } %></td>
    <td>
      <% task.labels.each do |label| %>
        <%= label.name %>
      <% end %>
    </td>
  </tr>
<% end %>
```

## ラベルでタスクを絞り込みできるようにする
まずはタスク一覧画面にセレクトボックスを作成して、絞り込みたいラベルのidを`tasks_controller`の`indexアクション`に渡せるようにしましょう。

```erb:app/views/tasks/index.html.erb
<%= form_with url: tasks_path, method: :get, local: true do |form| %>
  <%= form.select("label_id", Label.pluck(:name, :id), { include_blank: true }) %>
  <%= form.submit 'Search', name: nil %>
<% end %>
```
　
controller側はこのように書きます。

```ruby:app/controllers/tasks_controller.rb
def index
  @tasks = Task.all

  @tasks = @tasks.joins(:labels).where(labels: { id: params[:label_id] }) if params[:label_id].present?
end
```

`@tasks.joins(:labels)`でlabelsテーブルを結合します。
そして、`where(labels: { id: params[:label_id] })`で、先ほど設定したセレクトボックスから送られてきた`params[:label_id]`と、idが一致するラベルを持つタスクを取得できます。

# 完成イメージ
![label.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344685/15828646-4964-4087-c5b2-0958e7ddc762.gif)


# 参考
- [Ruby on Rails API](https://api.rubyonrails.org/)
- [Active Record の関連付け (アソシエーション) - Rails ガイド](https://railsguides.jp/association_basics.html#has-many-through%E9%96%A2%E9%80%A3%E4%BB%98%E3%81%91)