Rails+BootstrapでTodoアプリを作ってみよう!


はじめに

こんにちは!
DMM WEBCAMP Advent Calendar 2020の8日目を担当することになりました。
メンターの@ta-igaです。

プログラミングを初めてまず最初に作るアプリはtodoアプリなのではないでしょうか。一度理解してしまえばなんてことはない簡単なアプリですが理解するまでが意外と大変だと思います。そんなtodoアプリをrailsとbootstrapを使って制作していきましょう!

完成品はこちら↓
https://railsboottodo.herokuapp.com/tasks

バージョン

Rails 6.0.3.4
Ruby 2.6.3
Bootstrap 3.3.6

MVCとルーティング

まず最初にアプリの構造の説明をしていきます。皆さんMVC処理の流れとか説明できますか?MVCとはModel、Veiws、Controllerの3つの頭文字を取ったものです。アプリを利用する際、自分が指定したページ(URL)にいくまでパソコンの中では、Routing→Controller→Model→Viewsという順番に情報が処理されます。これをまず頭に入れておきましょう。これから複数の機能を作成していきますが、基本的にこの4つのページを編集していきます。

それぞれの機能について簡単に説明していきます。

ルーティング(Routing)
ユーザー側がブラウザを経由してアクセスしたURLとコントローラーのアクションを結びつける役割。

コントローラー(Controller)
ユーザーからのリクエストをModelやViewと連携させる役割。司令塔のようなところ。1つのControllerの中にユーザーからのリクエストに対して具体的な処理を実行する「アクション」が用意されている

モデル(Model)
データベースを管理するところ。コントローラーからの指示を受けデータの削除や追加をなどを行う。

ビュー(Views)
最終的な描画を担当するところ。私たちが普段パソコンで目にしている画面がこれになります。モデルからひっぱり出された情報を元に画面を表示しています。

ざっくりとこんな感じです。
こちらの記事だと画像とか使って詳しく書いてあるので参考にしてみてください。(著作権とかよくわかんないのでこの記事では画像使えませんでした🙇‍♂️)

プロジェクトの作成

それでは早速進めていきましょう。ターミナルに

ターミナル
$rails new todo-app

と打ってプロジェクトを作成させます。途中、passwordを求められますので忘れずに入力してください。その際、入力した文字が画面に表示されませんが構わずに続けてEnterを押して進んでください。

Webpacker successfully installed 🎉 🍰

と表示されればプロジェクト作成は完了です。ケーキ食べたい...(途中Warningがたくさん出てくるかもしれませんがErrorと出てこない限りは全部無視してしまって大丈夫です。)その後

ターミナル
$cd todo-app

と入力してtodo-appに移動しましょう。(この後の作業は全てtodo-appに移動している前提で行います。)

モデルの作成

モデルを作成していきます。モデルは単数形で頭文字を大文字にするのがルールです。ここではtitleをstring型、bodyをtext型にして作っていきたいと思います。ターミナルで以下を実行してください。

ターミナル
$rails g model Task title:string body:text

できたらその後以下でマイグレートしましょう。(もし間違えて作成してしまった場合には、gをdに変えて実行すれば消すことができます。)

ターミナル
$rails db:migrate

コントローラの作成

コントローラを作ります。コントローラは複数形で全て小文字にするのがルールです。

ターミナル
rails g controller tasks

これで、大まかな構造を作ることができました。(もし間違えて作成してしまった場合には、モデルと同じくgをdに変えて実行すれば消すことができます。)

トップ画面 ~index~

まずトップ画面を作成していきたいと思います。トップ画面にはやること一覧を表示させます。これにはindexアクションを用いましょう。ここではすべてのタスクをTask.allで取得します。

app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  def index
    @tasks = Task.all
  end
end

次にviewを作成していきましょう。viewsフォルダのなかのtasksフォルダの中にindex.html.erbというファイルを作りましょう。(controlを押しながらクリックすれば作成できます。)ファイルを作ることができたら以下を記述しましょう。

app/views/tasks/index.html.erb
<h1>やること一覧</h1>

<table>
  <thead>
    <tr>
      <th>やること</th>
      <th>詳細</th>
    </tr>
  </thead>

  <tbody>
    <% @tasks.each do |task| %>
    <tr>
      <td><%= task.title %></td>
      <td><%= task.body %></td>
    </tr>
    <% end %>
  </tbody>
</table>

次に大事なルーティングを作成しましょう。以下のように記述してください。

config/routes.rb
resources :tasks

なんとなんとrailsだとこの1行でindex,new,create,edit,update,show,destroyの主要な7つのアクション一気に作ることができます。(今回showは使いませんが...)rails便利!

これからは、localhost:3000/tasksと打てば今作成している画面を確認できるようになります。エラーが起きていないか常に確認しながら進めていきましょう。

新規作成機能 ~new,create~

ここからは新規作成機能を作っていきましょう。まずはコントローラに記述をしていきます。

app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  def index
    @tasks = Task.all
  end

#ここから追加
  def new
    @task = Task.new
  end

  def create
    @task = Task.new(task_params
    if @task.save
      redirect_to tasks_path #セーブできたらindexページに行く
    else
      render 'new' #できなかったらnewページのまま
    end
  end

  private
  def task_params
  #モデル作成時に作ったやつ
    params.require(:task).permit(:title, :body)
  end
end

ここではストロングパラメータというものを使って、Taskモデルを作成した際にできたtaskテーブルににtitle(やること)とbody(詳細)を保存します。ストロングパラメータについて詳しく知りたいかたはこちらを参照してください。そして「redirect toとrenderの違いって何??」と思ったセンスの良いかたはこちらを参照してください。

viewを作成します。

app/views/tasks/new.html.erb
<h1>やること新規作成</h1>

<%= link_to '一覧へ', tasks_path %>

<%= form_with model: @task do |f| %>
<div class="field">
  <%= f.label :やること %><br>
  <%= f.textarea_field :title %>
</div>
<div class="field">
  <%= f.label :詳細 %><br>
  <%= f.text_field :body %>
</div>
<div class="actions">
  <%= f.submit %>
</div>
<% end %>

form_withを使ってデータを送信させます。form_withについて詳しく知りたいかたはこちら
これによって送信されたデータは、createアクションへ運ばれます。

ここで、フォームが空欄だった場合に送信できないようにしておきましょう。この機能をバリデーションと呼びます。

app/model/task.rb
class Task < ApplicationRecord
    validates :title, presence: true
    validates :body, presence: true
end

詳しい説明はここでは省きますが、これで空欄のままsubmitを押しても送信されないようになります。バリデーションについて詳しく知りたい方はこちら

最後にindexのページ(やること一覧のページ)に新規作成ページへのリンクを貼り付けましょう。ターミナルで

ターミナル
$rails routes

と打ってみてください。すると、

ターミナル
     tasks  GET    /tasks(.:format)  tasks#index                                                              
         POST   /tasks(.:format)  tasks#create                                                              
   new_task GET    /tasks/new(.:format)  tasks#new                                                          
    edit_task GET    /tasks/:id/edit(.:format)   tasks#edit                                                   
         task GET    /tasks/:id(.:format)    tasks#show                                                       
              PATCH  /tasks/:id(.:format)    tasks#update                                                       
              PUT    /tasks/:id(.:format)     tasks#update                                                      
              DELETE /tasks/:id(.:format)      tasks#destroy 
  ...                                                    

と出てきたと思います。このtasks#newの左側にあるnew_taskにpathをくっつけたのがnewページに行くためのリンクになります。そのため、

app/views/tasks/index.html.erb
<h1>やること一覧</h1>

<%= link_to '新規作成', new_task_path %> #追加

<table>
  <thead>
    <tr>
      <th>やること</th>
      <th>詳細</th>
    </tr>
  </thead>

  <tbody>
    <% @tasks.each do |task| %>
    <tr>
      <td><%= task.title %></td>
      <td><%= task.body %></td>
    </tr>
    <% end %>
  </tbody>
</table>


これで新規作成機能は終わりです。

編集機能 ~edit,update~

続いて、編集機能を作成していきたいと思います。編集にはeditアクションとupdateアクションを使用します。コントローラーにこの2つのアクションを記述していきましょう。

app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  def index
    @tasks = Task.all
  end

  def new
    @task = Task.new
  end

  def create
    @task = Task.new(task_params)
    if @task.save
      redirect_to tasks_path
    else
      render 'new'
    end
  end

 #ここから追加
  def edit
    @task = Task.find(params[:id])
  end

  def update
    @task = Task.find(params[:id])
    if @task.update(task_params) 
      redirect_to tasks_path #updateできたらindexページに行く
    else
      render 'edit' #できなかったらeditページのまま
    end
  end
 #ここまで

  private
    def task_params
      params.require(:task).permit(:title, :body)
    end
end

ここで新しく、find(params[:id])というコードが出てきましたね。これは投稿され情報に付けられたidを探し出すコードです。例えば、「ケーキを買う」という内容を編集したいのにボタン押したら隣に記述していた「部屋の掃除をする」という内容が画面に出てきたらもう訳わかんないですよね。ここではこのfind(params[:id])を使って編集したい内容を正しく呼び出せるようにしています。(ちなみにrailsというフレームワークを使っているからこの1行だけで情報を呼び出せるのであり、使はなければSQLという言語をゴリゴリ書く必要があります。railsすげえ。)

次にviewsファイルを作成していきましょう。

app/views/tasks/edit.html.erb

<h1>やること編集</h1>

<%= form_with model: @task do |f| %>
<div class="field">
  <%= f.label :やること %><br>
  <%= f.text_field :title %>
</div>
<div class="field">
  <%= f.label :詳細 %><br>
  <%= f.text_field :body %>
</div>
<div class="actions">
  <%= f.submit %>
</div>
<% end %>

入力フォームなのでnew.html.erbと同じコードですね。最後に一覧画面に編集画面へのリンクを付け加えて終了です。

app/views/tasks/index.html.erb
<h1>やること一覧</h1>

<%= link_to '新規作成', new_task_path %>

<table>
  <thead>
    <tr>
      <th>やること</th>
      <th>詳細</th>
    </tr>
  </thead>

  <tbody>
    <% @tasks.each do |task| %>
    <tr>
      <td><%= task.title %></td>
      <td><%= task.body %></td>
      <td><%= link_to '編集', edit_task_path(task) %></td> #追加
    </tr>
    <% end %>
  </tbody>
</table>

pathはターミナルでrails routesと打って確認してみてください。(新規作成機能作る時に一回やったやつ。)edit画面へ遷移する時にはidが必要なので今この情報を持っているtaskを最後につけることを忘れずに。

これで編集機能は終わりです。

削除機能 ~destroy~

最後は削除機能を作成していきます。コントローラーにdestroyアクションを追加しましょう。

app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  def index
    @tasks = Task.all
  end

  def new
    @task = Task.new
  end

  def create
    @task = Task.new(task_params)
    if @task.save
      redirect_to tasks_path
    else
      render 'new'
    end
  end

  def edit
    @task = Task.find(params[:id])
  end

  def update
    @task = Task.find(params[:id])
    if @task.update(task_params) 
      redirect_to tasks_path
    else
      render 'edit'
    end
  end

 #ここから追加
 def destroy
    @task = Task.find(params[:id])
    @task.destroy
    redirect_to tasks_path
  end
 #ここまで

  private
    def task_params
      params.require(:task).permit(:title, :body)
    end
end

ここではfind(params[:id])で削除する情報を見つけて、destroyで削除しています。
一覧画面に削除ボタンを付けましょう。

app/views/tasks/index.html.erb
<h1>やること一覧</h1>

<%= link_to '新規作成', new_task_path %>

<table>
  <thead>
    <tr>
      <th>やること</th>
      <th>詳細</th>
    </tr>
  </thead>

  <tbody>
    <% @tasks.each do |task| %>
    <tr>
      <td><%= task.title %></td>
      <td><%= task.body %></td>
      <td><%= link_to '編集', edit_task_path(task) %></td>
    <td><%= link_to '削除', task, method: :delete, data: { confirm: 'You sure?' } %></td>
    </tr>
    </tr>
    <% end %>
  </tbody>
</table>

ここではpath名ではなく、method: :deleteと記述することでdestroyアクションを呼びます。そして、data: { confirm: 'You sure?' }と記述することで確認のモーダルウィンドウを出すことができます。

以上でtodoアプリの機能は全て作ることができました。お疲れ様でした。🍰

Bootstrapを使ってレイアウトを整えよう

これでtodoアプリは完成したのですが、レイアウトがちょっと味気ないですよね...そこでBootstrapというcssのフレームワークを導入してレイアウトを整えていきましょう!

ここではざっくりとしか説明しないので詳しく知りたい方はこちらを参照してみてください。

下準備

Gemfile
gem 'bootstrap-sass', '~> 3.3.6'
gem 'jquery-rails'

と入力し、

ターミナル
 $bundle install

としましょう。途中passwordを求められると思うので指示通り入力してください。

次にRailsアプリの作成時に生成されるapplication.cssapplication.scssにリネームしてください。このファイルはapp/assert/stylesheet/の下にあります。

ファイル名を変更できたら、

app/assert/stylesheet/application.scss
 @import "bootstrap-sprockets"; 
 @import "bootstrap";

これらを追加してください。

そして、最後にapplication.jsを編集します。

app/javascript/application.js
//= require jquery
//= require bootstrap-sprockets

またこれらをを追加してください。これでrailsでbootstrapを使うための下準備が完了しました。

レイアウトを整える

早速やっていきましょう。

まず、画面が左に偏っていると思うので真ん中にいい感じになるようにしましょう。(語彙力なくてごめんなさい。)application.html.erbにある<%= yield %>を以下のようにdivタグで囲んで下さい。

views/layout/application.html.erb
  <div class="container">
    <%= yield %>
  </div>

この<%= yield %>には、各ページで表示されるhtmlファイルの内容が入ってきます。そのため、ここを編集するだけで全ページに編集を適用させることができます。

これで、レイアウトがいい感じに画面の真ん中にあると思います。

次に一覧ページのtableタグを以下のように編集してください。

app/views/tasks/index.html.erb
 <table class="table table-striped">

 </table>

これで投稿された内容の背景が交互に変わり見やすくなっていると思います。(これすごいですよね。初めて使った時感動しました。)

そして最後にボタンをいい感じにしておきましょう。一覧画面にある、新規作成ボタン、編集ボタン、削除ボタンを以下のようにclassを付けて編集してみてください。

app/views/tasks/index.html.erb

<%= link_to '新規作成', new_task_path, class: "btn btn-primary" %>
<%= link_to '編集', edit_task_path(task), class: "btn btn-success"  %>
<%= link_to '削除', task, method: :delete, data: { confirm: 'You sure?' }, class: "btn btn-danger" %>

ついでに新規作成画面と編集画面にある「一覧へ」ボタンも編集してみましょう。

app/views/tasks/new.html.erbとedit.html.erb
<%= link_to '一覧へ', tasks_path, class: "btn btn-info" %>

フォームのボタンも編集することができます。こちらのサイトに色々載ってあるので余裕のある方はやってみてください。

これで使いやすいいい感じのレイアウトになったと思います。お疲れ様でした!

最後に

これで良さげなtodoアプリを作ることができました。たかがtodoアプリではありますが重要な要素がたくさん詰まっているので1つ1つ確認してみてください。

以上、最後まで読んでいただきありがとうございました!!!🍰🍰