Rails 爆速でtag機能を搭載したtodoアプリを作ってみよう（docker構築有）

# 今日の目標
爆速でtag機能を搭載したミニアプリを制作してみる

まずは基本機能のみのアプリを作り　追加実装を意識した作りをする

## 環境
Ruby    2.5.1
Rails   5.2.4
docker　19.03.8

##今回はdockerで環境構築します。
dockerなんて　訳わからんと言う人はdockerの構築部分は読み飛ばしてください

## githubでリポジトリも作成します
githubなんて知らんわ　って人も読み飛ばしてください

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

## dcocker構築
[docker公式](http://docs.docker.jp/)

## 
まずディレクトリを作成

```
$ mkdir todo-tag-app
$ cd todo-tag-app
```
githubで新しいレポジトリを作成

[![Image from Gyazo](https://i.gyazo.com/15e321f05a755bb5b243186ebe8bd5ea.png)](https://gyazo.com/15e321f05a755bb5b243186ebe8bd5ea)

名前は適当で良いが今回は`todo-tag-app`とする

[![Image from Gyazo](https://i.gyazo.com/e0157f0a6ffca83cbbdce2e9a3aab32d.png)](https://gyazo.com/e0157f0a6ffca83cbbdce2e9a3aab32d)

Create repositoryすると

[![Image from Gyazo](https://i.gyazo.com/e7bde0cb27957fa077d4df67f8efd330.png)](https://gyazo.com/e7bde0cb27957fa077d4df67f8efd330)

ガイダンスが出てくるので　この通りにコマンド打てばgit連携できます

```
$ git init
```
今回は機能拡張するかもしれないので　ReadMeも作っておきましょう

```
$ echo "# todo-tag-app" >> README.md

$ git add README.md

```
結果にcommit

```
$ git commit -m "first commit"
```


ファイルをリポジトリに

```
$ git remote add origin git@github.com:higakikeita/todo-tag-app.git
``` 
```
$ git push -u origin master
```
無事　Githubと連携できました
[![Image from Gyazo](https://i.gyazo.com/b197129097eb277281fb55b9678a9359.png)](https://gyazo.com/b197129097eb277281fb55b9678a9359)

# dockerの構築
## docker-file作成

```
$ touch Dockerfile
```
VscodeでDockerfileが作られていることを確認

[![Image from Gyazo](https://i.gyazo.com/bb7da64642d427bfa53f072e380902a9.png)](https://gyazo.com/bb7da64642d427bfa53f072e380902a9)

dockerfileを以下のように編集


```ruby:Dockerfile
FROM ruby:2.5.1

# 必要なパッケージのインストール（基本的に必要になってくるものだと思うので削らないこと）
RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs           

# 作業ディレクトリの作成、設定
RUN mkdir /app_name 

# 作業ディレクトリ名をAPP_ROOTに割り当てて、以下$APP_ROOTで参照

ENV APP_ROOT /app_name 
WORKDIR $APP_ROOT

# ホスト側（ローカル）のGemfileを追加する（ローカルのGemfileは【３】で作成）
ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

# Gemfileのbundle install
RUN bundle install
ADD . $APP_ROOT
```
結果にcommit 

```
$ git add .
$ git commit -m "Dockerfileの作成"
```
## Gemfileの作成

```
$ touch Gemfile
```
```ruby:Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.4'
```
## Gemfile.lockの作成

```
$ touch Gemfile.lock
```
結果にcommit 

```
$ git add .
$ git commit -m "Gemfileの作成"
```

## docker-compose.ymlの作成

```
$ touch docker-compose.yml
```

```ruby:docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "3306:3306"

  web:
    build: .
    command: rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app_name
    ports:
      - "3000:3000"
    links:
      - db

```
結果にcommit

```
$ git add .
$ git commit -m "docker-compose.yml"
```
ここまでで　アプリケーション作成のためのgithubとdockerの構築はひとまず完了

```
$ git push
```
## Rails new

```
$ docker-compose run web rails new . --force --database=mysql --skip-bundle
```
結果にcommit 

```
$ git add .
$ git commit -m "Rails作成"
```
## database.ymlの編集

```yml:config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # docker-compose.ymlのMYSQL_ROOT_PASSWORD
  host: db # docker-compose.ymlのservice名
```
ここまでで一旦　結果にcommit & pushする

```
$ git add .
$ git commit -m "database.ymlの編集”
$ git push
```
## dockerを起動する

```
$ docker-compose build # コンテナをビルド

# -dオプションをつけてバックグラウンド実行するとこの後新しいシェルを立ち上げる必要がなくなる
$ docker-compose up # コンテナの一斉起動
```
dockerのコマンドの基本はこちらでどうぞ
[docker-composeコマンドの備忘録](https://qiita.com/daijiro_maeyama/items/f0569a26fe4885b51268)
https://qiita.com/tegnike/items/bcdcee0320e11a928d46

## DB作成
新しいターミナルを開いて　`/todo-tag-app`　で実行

```
$ docker-compose run web rails db:create
```
## localhost:3000でサーバの起動を確認する

[![Image from Gyazo](https://i.gyazo.com/79ed9aa49eea56a6123fc4f699b9bd9a.png)](https://gyazo.com/79ed9aa49eea56a6123fc4f699b9bd9a)

Railsが皆さんを歓迎してくれていますね。

ここまでで環境構築は完了です。いよいよアプリケーションを作っていきます。

## 爆速でtodoアプリを作成する

それでは　爆速でtodoアプリを作成していきます

まずTaskモデル・コントローラー・ビューをscaffoldで一括生成します。

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

```
$ rails generate scaffold label name:string
```

結果にcommit

```
$ git add .
$ git commit -m "タスク、ラベルモデルの作成"

```
今回は多対多の関連付けを行うので、結合モデル・中間テーブル（交差テーブル）を作りましょう。

```
$ rails generate model labelling task:references label:references 
```
ここで　一旦　マイグレーションを実行します

```
$ docker-compose run web rails db:migrate
```

[![Image from Gyazo](https://i.gyazo.com/f991cc7f56a5c5f0bdd896ae45d5a70a.png)](https://gyazo.com/f991cc7f56a5c5f0bdd896ae45d5a70a)

[![Image from Gyazo](https://i.gyazo.com/caa3eb705c6f7f97d6eb7d6702acec43.png)](https://gyazo.com/caa3eb705c6f7f97d6eb7d6702acec43)

[![Image from Gyazo](https://i.gyazo.com/532e099f03a2a1ca17c3b06ac4eb417b.png)](https://gyazo.com/532e099f03a2a1ca17c3b06ac4eb417b)

schema.rbが以下のようになっているか確認してください
[![Image from Gyazo](https://i.gyazo.com/9cc50ac1b322463143a0e7093db98998.png)](https://gyazo.com/9cc50ac1b322463143a0e7093db98998)

ここまで確認したら　結果にcommit 

```
$ git add .
$ git commit -m "テーブルの作成"
```


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

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
```
中間テーブルのlabellingモデル

```ruby:app/models/labelling.rb
class Labelling < ApplicationRecord
  belongs_to :task
  belongs_to :label
end
```
ここでまた　結果にcommit

```
$ git add .
$ git commit -m "アソシエーションの設定"
$ git push

```
##ルーティングの設定

```ruby:config/route.rb
Rails.application.routes.draw do
  root 'tasks#index'
  resources :labels
  resources :tasks
  
end
```
ここで一度サーバを再起動し、ビューを確認します。

```
$ docker-compose up --build
```
[![Image from Gyazo](https://i.gyazo.com/56af551d80cc91865466be304b4ea6b5.gif)](https://gyazo.com/56af551d80cc91865466be304b4ea6b5)

## タスク作成・更新時にラベルを関連付けられるようにする

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

```erb:app/views/tasks/_form.html.erb
<%= form.collection_check_boxes(:label_ids, Label.all, :id, :name) %>
```

```erb:app/views/tasks/index.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 %>
```
まめに結果にcommitしておきましょう

```
$ git add .
$ git commit -m "ラベル表示の実装"
```

## ラベルでタスクを絞り込めるようにする
まずはタスク一覧画面にセレクトボックスを作成して、絞り込みたいラベルの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が一致するラベルを持つタスクを取得できます。

結果にcommit

```
$ git add .
$ git commit -m "ラベル絞り込みの実装"
$ git push
```
## ページ遷移の確認
ラベル登録ページとタスク登録ページが今のままだと遷移できないので　link_to で遷移できるようにします。

pathを確認

```
$ rails routes
```

[![Image from Gyazo](https://i.gyazo.com/69d65272132a7b03b528a119956daf53.png)](https://gyazo.com/69d65272132a7b03b528a119956daf53)

タスク一覧ページは　`tasks_path` ラベル一覧ページは　`labels_path`

```erb:app/view/tasks/index.html.erb
<p id="notice"><%= notice %></p>

<h1>Tasks</h1>

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th colspan="3"></th>
    </tr>
  </thead>
      <%= 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 %>

  <tbody>
    <% @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 %>
  </tbody>
</table>

<br>

<%= link_to 'New Task', new_task_path %>
<%# この行を追記 %>
<%= link_to "ラベルを登録する",labels_path%>

``` 
<%= link_to "ラベルを登録する",labels_path%>を追記します

```erb:app/views/labels/index.html.erb
<p id="notice"><%= notice %></p>

<h1>Labels</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @labels.each do |label| %>
      <tr>
        <td><%= label.name %></td>
        <td><%= link_to 'Show', label %></td>
        <td><%= link_to 'Edit', edit_label_path(label) %></td>
        <td><%= link_to 'Destroy', label, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Label', new_label_path %>
<%# ここから追記 %>
<%= link_to "タスクを登録する",tasks_path%>

```
最後に結果にcommit しておきましょう

```
$ git add .
$ git commit -m "画面遷移の調整"
$ git push
```
# 完成品

[![Image from Gyazo](https://i.gyazo.com/6a4ff901fb2e4d16c55a3d58fc563860.gif)](https://gyazo.com/6a4ff901fb2e4d16c55a3d58fc563860)

基本機能は以下です
タスク作成時・更新時に、事前に作成したラベルを複数付けられる
タスクに関連付けられているラベルを表示できる
タスクをラベルで絞り込みできる

ここからログイン機能を付けたり、カレンダー機能と連携させたり、マークダウン を導入したり　いろいろ出来るかと思います。
ビューもいじると結構見栄えのするアプリになるんじゃないかと思います。

檜垣慶太
https://twitter.com/keitah0322