Rails 6 でTodoApp作るぜ part2

今回は、TodoAppの本体Taskモデル・Tasksコントローラー・各種ビューを作る

Taskモデル作成

テーブル設計

名称 カラム名 データ型
タイトル title string
内容 description text

めちゃめちゃシンプルに!

% rails g model Task title:string description:text

こんな感じ(nullつけ忘れました)

class CreateTasks < ActiveRecord::Migration[6.0]
  def change
    create_table :tasks do |t|
      t.string :title
      t.text :description

      t.timestamps
    end
  end
end
% rails db:migrate


nullはそのうちつける・・・

コントローラーとビュー

シンプルなCRUDってやつ!

% rails g controller tasks index show new edit

まずはRoutesを設定


[ config/routes.rb ]

Rails.application.routes.draw do

  root 'tasks#index'
  get 'tasks/index', to: 'tasks#index'
  resources :tasks
end
  • resourcesで名前付きルートやら一括設定

  • rootでroot設定だぞ!!!

  • 事件が発生した。。

tasks/indexにアクセスするとtasks/show.html.slimが表示されました。なので、get 'tasks/index', to: 'tasks#index'で押し通ります。

※ひとまず完成と言えるとこまでは止まっちゃダメ、絶対。


コントローラー

超シンプルなアプリなのでバババババ馬場馬っと書く
細かい修正は最後にやります。


[ 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_url, notice: "タスク「#{@task.title}」を登録しました。"
    else
      render :new
    end
  end

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

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

  def update
    @task = Task.find(params[:id])
    if @task.update(task_params)
      redirect_to tasks_url, notice: "タスク「#{@task.title}」を更新しました!"
    else
      render :edit
    end
  end

  def destroy
    @task = Task.find(params[:id])
    @task.destroy
    redirect_to tasks_url, notice: "タスク「#{@task.title}」を削除しました。"
  end

  private

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

このくらいは何も見なくても書けるようになりました(^^


flashメッセージを設定したので、ちゃんと表示されるようにviewに記述します


[ app/views/layouts/application.html.slim ]

doctype html
html
  head
    title
      | TodoApp
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
  body
    .container
      - if flash.notice.present?  # 追加
        .alert.alert-success = flash.notice  # 追加
      = yield

f:id:yukitoku_sw:20200117212818p:plain

こんな感じになります。


ビューズ

こんな感じにしていきます。

f:id:yukitoku_sw:20200117210947p:plain


[ tasks/index.html.slim ]

h1 タスク一覧

.nav.justify-content-end
  = link_to '新規登録', new_task_path, class: 'nav-link'

table.table
  thead.thead-default
    tr
      th タイトル
      th 登録日
      th 編集日
      th
  tbody
    - @tasks.each do |task|
      tr
        td = link_to task.title, task
        td = task.created_at
        td = task.updated_at
        td
          = link_to '編集', edit_task_path(task), class: 'btn btn-primary mr-3'
          = link_to '削除', task, method: :delete, data: { confirm: "タスク「#{task.title}」を削除します。よろしいですか?" }, class: 'btn btn-danger'

tasks/index.html.slimでは一覧機能を表示させます。 @tasksで取得したデータをeach doで全表示させる 表示内容は、とりあえず :title:created_at`:updated_atの3つとします。

一覧ページで編集へのリンクを貼る際は、edit_task_path(task)と引数を指定しないとエラーが出る


続いて、詳細ページ

f:id:yukitoku_sw:20200117211112p:plain


[ tasks/show.html.slim ]

h1 タスクの詳細

.nav.justify-content-end
  = link_to '一覧', tasks_path, class: 'nav-link'

table.table.table-hover
  tbody
    tr
      th タイトル
      td = @task.title
    tr
      th 内容
      td = @task.description
    tr
      th 登録日
      td = @task.created_at
    tr
      th 更新日
      td = @task.updated_at

= link_to '編集', edit_task_path, class: 'btn btn-primary mr-3'
= link_to '削除', @task, method: :delete, data: { confirm: "タスク「#{@task.title}」を削除します。よろしいですか?" }, class: 'btn btn-danger'

showページでは、内容(:description)も表示されるようにする また、show では@task = Task.find(params[:id])でデータを取得しているため、編集へのリンクに引数がいらない 逆に削除へのリンクではtaskではなく@taskにする


次は_form.html.slimを作成する


[ app/views/tasks/_form.html.slim ]

= form_with model: task, local: true do |f|
  .form-group
    = f.label :title, 'タイトル'
    = f.text_field :title, class: 'form-control', id: 'task_title'
  .form-group
    = f.label :description, '詳細'
    = f.text_area :description, class: 'form-control', id: 'task_description'
  = f.submit '登録', class: 'btn btn-success'

この書き方も暗記ですね。何も見なくても書けるようにします。 コントローラで設定した、task_paramsと内容を合わせることを忘れずに!

[ tasks_controller.rb ]

(省略)
 private

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

tasks_paramsの中のpermit(:title, :description)で設定してるカラムしか保存されない


そして、newとeditを実装する


f:id:yukitoku_sw:20200117211234p:plain

[ tasks/new.html.index ]

h1 タスク新規登録

.nav.justify-content-end
  = link_to '一覧', tasks_path, class: 'nav-link'

= render partial: 'form', locals: { task: @task }


f:id:yukitoku_sw:20200117211322p:plain


[ tasks/edit.html.index ]

h1 タスクの編集

.nav.justify-content-end
  = link_to '一覧', tasks_path, class: 'nav-link'

= render partial: 'form', locals: { task: @task }

ここでは、= render 'form'とはせずに、= render partial: 'form', locals: { task: @task }と記述しています。
こうすると「インスタンス変数@taskを、パーシャル内のローカル変数taskとして渡す」ことができます。
= render 'form'としたい場合は、

[ app/views/tasks/_form.html.slim ]

= form_with model: @task, local: true do |f|  #@taskに変更する
  .form-group
    = f.label :title, 'タイトル'
    = f.text_field :title, class: 'form-control', id: 'task_title'
  .form-group
    = f.label :description, '詳細'
    = f.text_area :description, class: 'form-control', id: 'task_description'
  = f.submit '登録', class: 'btn btn-success'

model: taskmodel: @taskにする必要があります。

以上でシンプルなCRUD機能の完成です。


ヘッダーの作成

ちょっと寂しいので簡単なヘッダーを作ります

ファイルapp/views/layouts/_headerを作成します。


[ layouts/_header ]

header.mb-3
  .app-title.navbar.navbar-expand-md.navbar-dark.bg-dark
    .navbar-brand TodoApp


そして、application.html.slimのbodyの下(.containerの上)にrenderします。

[ layouts/application.html.slim ]

doctype html
html
  head
    title
      | TodoApp
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_pack_tag 'application', 'data-turbolinks-track': 'reload'
  body
    = render 'layouts/header'  # 追加
    .container
      - if flash.notice.present?
        .alert.alert-success = flash.notice
      = yield


f:id:yukitoku_sw:20200117212320p:plain

ひとまず、よしっ

モデルに検証機能を加える

今のままだと、新規登録画面で何も書かなくても登録できてしまいます。それだと困る・・ことは現状ないですが、実際にwebサービスとして運用するとなるとあまりよろしくありません。
なので:title:descriptionには何も入力されていなければエラーになるように設定します。

[ app/models/task.rb ]

class Task < ApplicationRecord
  validates :title, presence: true, length: { maximum: 30 }
  validates :description, presence: true
end

presence: trueとすることで入力必須項目となり、エラーを発生させることができます。
titleの方にはlength: { maximum: 30 }も気分でつけてみました。これにより、タイトルは30文字以内にしないとエラーが出るようになります。このTodoAppは私の思うがままです。(笑)


このままでは、コンソールを開かないとなんのエラーが出たのかわからないのでviewで表示されるように設定します。

[ app/views/tasks/_form.html.slim ]

- if task.errors.present?
  ul#error_explanation
    - task.errors.full_messages.each do |message|
      li = message
      
= form_with model: task, local: true do |f|
  .form-group
    = f.label :title, 'タイトル'
    = f.text_field :title, class: 'form-control', id: 'task_title'
  .form-group
    = f.label :description, '詳細'
    = f.text_area :description, class: 'form-control', id: 'task_description'
  = f.submit '登録', class: 'btn btn-success'

formに入力された値が正しくなければエラーを出す。ということで_form.html.slimにブチ込みました。

もしエラーがあれば、それらのエラーをあるだけ全部伝えてくれ!
という内容ですね。


今回はここまで。


次回 User作る