Help us understand the problem. What is going on with this article?

[Ruby on Rails] チュートリアル的にToDoアプリを作ってみた話

More than 1 year has passed since last update.

はじめに

Ruby on Rails を手っ取り早く学習したいと思ってToDoアプリを作りました。
作っていく中で個人的にハマったところなどを忘れないうちにメモっておきたいと思います。

ちなみにこの記事は fukuokarb 中に書きました。
初参加でしたが、今後とも継続して参加していきたいと思っております。

成果物

こんなの作りました。
どこからどう見ても CRUD のお勉強をするのに最適とされるToDoリストアプリですね。
スクリーンショット 2018-10-10 16.21.13.png

構築

開発環境の構築手順についてはこちら をご覧ください。

データベース準備

この辺はご多分に漏れずMigrationファイル作成→Migrate実行という流れになります。
事前に rake db:create を行ってデータベースの生成を行っておきましょう。

console
# Migrationファイル生成
rails g migration create_tasks

# モデル生成
rails g model task

なお、Railsの規約によるとモデルは単数形、テーブルは複数形とのこと。
モデルは new などの処理で単数を扱うから、テーブルは複数のレコードを持つからそれぞれそういう区分けになっているみたいです。
この辺はLaravelも一緒ですね。
続いてMigrationファイルを修正していきます。

db/migrate/yyyymmddhhiiss_create_tasks.db
class CreateTasks < ActiveRecord::Migration[5.2]
  def change
    create_table :tasks do |t|
      # 設定できるもの
      # string     : 文字列
      # text       : 長い文字列
      # integer    : 整数
      # float      : 浮動小数
      # decimal    : 倍精度小数
      # datetime   : 日時
      # timestamp  : タイムスタンプ
      # timestamps : created_at, updated_at
      # time       : 時間
      # date       : 日付
      # binary     : バイナリデータ
      # boolean    : Boolean

      t.text :state
      t.text :task
      t.date :limit_date

      t.timestamps
    end
  end
end

続いてテストデータの作成。
手で書いてもいいけど… Seeder から流したほうが楽ですよね。

db/seeds.rb
@task              = Task.new
@task.task         = 'task1'
@task.state        = 'todo'
@task.limit_date   = '2018-10-10'
@task.save

最後にMigrate実行、Seedingを行います。

console
rake db:migrate
rake db:seed
# ミスった場合は次のコマンドを投入
rake db:rollback

コントローラー生成

次にコントローラーを生成してきます。
ちなみにコントローラーは複数のリソースを一つのファイルで扱うので複数形

console
# コントローラーだけを生成する場合
rails g controller tasks

# メソッドも含めてスケルトンを生成する場合
rails g controller tasks index show store update destroy

ルート修正

続いてコントローラーを修正していきます。
RESTfulに書きたかったので。

config/routes.rb
Rails.application.routes.draw do
  get    'tasks'     => 'tasks#index'
  post   'tasks'     => 'tasks#store'
  get    'tasks/:id' => 'tasks#show'
  put    'tasks/:id' => 'tasks#update'
  delete 'tasks/:id' => 'tasks#destroy'
  # この書き方でもOKとのこと
  # get '/tasks', to: 'tasks#index'
end

間違いなくもっと効率的なルートの記述方法があるので、また調べて追記します。

追記

こちら にいい感じの記事がありました。

サーバーの起動

ここでサーバーを起動してみます。
なお、バックエンドの修正を行った場合、毎回止めてサーバーの再起動をしないといけない…?

console
bin/rails server -b 0.0.0.0 -p 2000

Viewファイル修正

それぞれのファイルを修正していきますが、細かい部分については下の「各種機能の実装」に記載します。
ただ少し注意点が。

View内コード実行

index.html
<% @tasks.each do |t| %>
  <p><%= t.task %></p>
<% end %>

フォームデータを送る場合

Laravelで言うところの csrf_token 的なものを記載しておかないと怒られます。
Railsでは authenticity_token と呼ばれているようですね。
これは form_authenticity_token という関数で生成できるみたいです(参考)。
次のように記述しましょう。

ruby_form.html
<form method="POST" action="/tasks">
  <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
</form>

参考:Laravelの場合

php_form.html
<form method="POST" action="/tasks">
  {{ crsf_fileld() }}
</form>

PUT(PATCH), DELETEをする場合

これまたLaravelで言うところのヘルパ関数、 method_field('method') 的なものを用意しておかないと全て POST(GET) で送られてしまいます。
Railsでも _method という隠し要素を送ることで問題なくパスできます。

ruby_form.html
<form method="POST" action="/tasks">
  <input type="hidden" name="_method" value="PUT">
  <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
</form>

参考:Laravelの場合

php_form.html
<form method="POST" action="/tasks">
  {{ crsf_fileld() }}
  {{ method_field('PUT') }}
</form>

各種機能の実装

タスク一覧取得(GET)

まずはindexページに飛ばします。
スクショに貼り付けている画面ですね。
ルーティングは /tasks 、 コントローラーメソッドは index

Controller実装

app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  def index
    @tasks  = Task.order('limit_date').all
    @status = ['todo', 'doing', 'done']  
  end
  # ...
end

特に難しいことはやっていませんが、期限の近いものから順に並べたデータを取得しています。
細かい検索などをやりたければ こちら の記事が参考になるかと。
ちなみに @tasks としているのは、インスタンス変数扱いにしてViewに引き渡すためのようです(参考)。
Laravelだと return view('tasks.index', ['tasks' => $tasks]) と書いていました。
記述を簡略化できる半面、少し慣れるまでに時間がかかりそうという印象。

View実装

app/views/tasks/index.erb
<table>
  <thead>
    <tr>
      <th>State</th>
      <th>Limit</th>
      <th>Task</th>
      <th></th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <% @tasks.each do |t| %>
    <tr>
      <td><%= t.state %></td>
      <td><%= t.limit_date %></td>
      <td><%= t.task %></td>
      <td>
        <!-- 次のタスク詳細取得画面へのリンク -->
        <a href="/tasks/<%= t.id %>">edit</a>
      </td>
      <td>
        <!-- まだ実装しない -->
         <button type="submit">delete</button>
      </td>
    </tr>
    <% end %>
  </tbody>
</table>

こちらも特に難しいことはやっていません。
デザイン部分は全て削ってあります。
DELETE 部分はあとで追記します。

タスク作成(POST)

app/views/tasks/index.erb に少し追記。
先頭にタスク作成フォームを追します。

View修正

app/views/tasks/index.erb
<form method="POST" action="/tasks">
  <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
  <div>
    <label for="task">Task Name</label>
    <input id="task" name="task" type="text" required>
  </div>
  <div>
    <select name="state">
      <% @status.each_with_index do |s,i| %>
      <option value="<%= s %>"><%= s %></option>
      <% end %>
    </select>
    <label>Task State</label>
  </div>
  <div>
    <input type="date" name="limit_date" required>
    <label>Task Limit Day</label>
  </div>
  <p>
    <button type="submit">create</button>
  </p>
</div>
</form>

<!-- flashメッセージを表示 -->
<% flash.each do |msg| %>
<p><%= msg %></p>
<% end %>

<table>...</table>

Controller実装

app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  # ...
  def store
    task = Task.new
    task.task       = params[:task]
    task.state      = params[:state]
    task.limit_date = params[:limit_date]
    task.save
    redirect_to '/tasks', notice: 'タスクを作成しました。'
  end
  # ...
end

redirect_to でindexページに差し戻しています(参考)。
差し戻す際に notice: 'message' として flash に情報を残しておくと一回こっきりで表示されるメッセージ(例:ユーザーを登録しました)をViewに引き渡せます(参考)。

タスク詳細取得(GET)

index.erb からのリンクで入ってきます。
ルーティングは /tasks/:id 、コントローラーメソッドは show

Controller実装

app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  # ...
  def show
    id      = params[:id]
    @task   = Task.find(id)
    @status = ['todo', 'doing', 'done']
  end
  # ...
end

こちらも何も難しいことはしておりません。
indexページから引き渡ってきたパラメータ(params)を使っているくらい。

View実装

app/views/tasks/show.erb
<form method="POST" action="/tasks/<%= @task.id %>">
  <input type="hidden" name="_method" value="PUT">
  <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
  <div>
    <label for="task">Task Name</label>
    <input value="<%= @task.task %>" id="task" name="task" type="text" required>
  </div>
  <div>
    <select name="state">
      <% @status.each_with_index do |s,i| %>
      <option value="<%= s %>"><%= s %></option>
      <% end %>
    </select>
    <label>Task State</label>
  </div>
  <div>
    <input type="date" name="limit_date" required>
    <label>Task Limit Day</label>
  </div>
  <p>
    <button type="submit">update</button>
  </p>
</form>

次のこともあるので、先にフォームだけ生成しておきます。
トークンとメソッドの上書きを忘れないこと。

タスク更新(PUT)

フォームは先に生成しているので省略。
こちらも特に難しいことはありません。
ルーティングは /tasks/:id 、 コントローラーメソッドは update

Controller実装

app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  # ...
  def update
    id   = params[:id]
    task = Task.find(1)

    task.task         = params[:task]
    task.state        = params[:state]
    task.limit_date   = params[:limit_date]
    task.save

    redirect_to '/tasks', notice: 'タスクを更新しました。'
  end
  # ...
end

ちなみにModelにアクセスするメソッドについては こちら を参考にしました。

タスク削除(DELETE)

はい、最後に削除処理です。
ルーティングは /tasks/:id 、 コントローラーメソッドはdestroy

View修正

app/views/tasks/index.erb
<table>
  ...
  <tbody>
    <% @tasks.each do |t| %>
    <tr>
      <td><%= t.state %></td>
      <td><%= t.limit_date %></td>
      <td><%= t.task %></td>
      <td>
        <!-- 次のタスク詳細取得画面へのリンク -->
        <a href="/tasks/<%= t.id %>">edit</a>
      </td>
      <td>
              <!-- ここを修正する -->
        <form method="POST" action="/tasks/<%= t.id %>">
          <input type="hidden" name="_method" value="DELETE">
          <input type="hidden" name="authenticity_token" value="<%= form_authenticity_token %>">
          <button type="submit">delete</button>
        </form>
      </td>
    </tr>
    <% end %>
  </tbody>
</table>

隠しメソッドを DELETE で送るためだけのフォームを実装。

Controller実装

app/controllers/tasks_controller.rb
class TasksController < ApplicationController
  # ...
  def destroy
    task       = Task.find(params[:id])
    task.destroy
    redirect_to '/tasks', notice: 'タスクを削除しました。'
  end
  # ...
end

特に悩むことなし。

終わりに

ね?簡単でしょ?
全ソースコードは こちら に置いています。

今後は これ をやっていき

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした