データベースを使ったアプリ作成 4 (Ruby on Rails の利用)

はじめに

今回は TODO リストを管理するアプリを作成する. 以下を修得することを目的とする.

  • 既存の実装 (CRUD) を応用し,アクションを追加する
  • フォームの送信項目の追加
  • 条件に合致するレコードの絞り込み
  • 画面の見せ方の変更

scaffold でサンプルの作成

まず,scaffold でアプリを作成する.名前は kadai とする (名前は変更して構わない).

$ cd ~/my_web_apps

$ rails new kadai --database=mysql
$ cd kadai

今回は JSON 関係は使わないので, Gemfile 内の jbuilder をコメントアウトしておく.

$ vi Gemfile

  # Build JSON APIs with ease [https://github.com/rails/jbuilder]
  #gem "jbuilder"          

rails new のオプションで --database=mysql を指定したので, MySQL (mariaDB) にアクセスするための情報をファイルに書く.

$ vi config/database.yml

  default: &default
     adapter: mysql2
     encoding: utf8mb4
     pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
     username:                                           <-- 入力
     password:                                           <-- 入力
     socket: /var/run/mysqld/mysqld.sock

scaffold でアプリを作成する.コントローラの名前は todo_list にする. テーブルには title (string 型), content (text 型) の 2 つのカラムを作成する.

$ rails g scaffold todo_list  title:string content:text

モデル (データベース) を作成する.

$ rails db:create
$ rails db:migrate

サーバを起動する.

$ rails s -b 0.0.0.0

例題

内容

アプリ (/todo_lists/) の見た目を以下のように変更し,全てのページが動作するようにしなさい.ビュー・コントローラ・ルーティングのファイル,CSS ファイル,およびページのスクリーンショット (4 枚,含む URL バー) を提出すること.

  • 「インデックスページ」を以下のようにすること
    • 各項目が 1 行で示されていること.
    • 各項目のタイトルを押すと,「詳細表示ページ」にページ遷移すること.
    • 操作のところに「編集」のリンクがあり,それをクリックすると「編集ページ」にページ遷移する.
    • 操作のところに「削除」のリンクがあり,それをクリックすると,当該データをデータベースから消去した後に,「インデックスページ」にリダイレクトすること.
    • ページの末尾に「追加」のリンクがあり,それをクリックすると「新規作成ページ」にページ遷移すること.

  • 「詳細表示ページ」は以下のようにすること.
    • タイトルと内容が表示されること.
    • ページの末尾に,インデックスページに戻るリンクを配置すること.

  • 「編集ページ」は以下のようにすること.
    • タイトルと内容を入力できるようにすること.
    • ページ末尾の「アップデート」リンクを押すと,データが変更され,「詳細表示ページ」にリダイレクトされること.

  • 「新規作成ページ」は以下のようにすること.
    • タイトルと内容を入力できるようにすること.
    • ページ末尾の「クリエイト」リンクを押すと,データが作成され,「詳細表示ページ」にリダイレクトされること.

考え方

ルーティング

scaffold でアプリを作成すると, config/routes.rb には resources :todo_lists だけ書かれている.

$ vi config/routes.rb

  Rails.application.routes.draw do
     resources :todo_lists
  end

この resources メソッドによって 7 つのルーティングが自動で設定される. rails routes コマンドで確認することができる.

$ rails routes | head -n 10

  Prefix              Verb   URI Pattern                        Controller#Action
  root                GET    /                                  todo_lists#index
  todo_lists          GET    /todo_lists(.:format)              todo_lists#index
                      POST   /todo_lists(.:format)              todo_lists#create
  new_todo_list       GET    /todo_lists/new(.:format)          todo_lists#new
  edit_todo_list      GET    /todo_lists/:id/edit(.:format)     todo_lists#edit
  todo_list           GET    /todo_lists/:id(.:format)          todo_lists#show
                      PATCH  /todo_lists/:id(.:format)          todo_lists#update
                      PUT    /todo_lists/:id(.:format)          todo_lists#update
                      DELETE /todo_lists/:id(.:format)          todo_lists#destroy

HTTP のメソッド (GET, POST) などと URL に対応して,コントローラのアクションが決められている. デフォルトでは削除は DELETE メソッドに結びついており,ビューで以下のように button_to で「削除ボタン」を作ると,それは内部で当該 id のデータに対する DELETE メソッドに変換される.結果として,コントローラの destroy アクションが呼ばれてデータが削除される.

<%= button_to '削除', todo_list, method: :delete %>

   -->  DELETE /todo_lists/:id     -->   todo_lists#destroy

また,以下のようにビューの button_to を link_to に変えただけでは,データの削除はされない. link_to は内部で GET メソッドに変換されるため,結果的にコントローラの show アクションが呼ばれてしまうことになる.

<%= link_to   '削除', todo_list, method: :delete %>

  --> GET /todo_lists/:id     -->   todo_lists#show

作成 (new_todo_list) や編集 (edit_todo_list) と同様に, GET メソッドで削除を行うためには,config/routes.rb の resources に以下のような追記を行い,削除用のパス (以下の例では delete2_todo_list) を作成する必要がある.

$ vi config/routes.rb

  Rails.application.routes.draw do
     resources :todo_lists do
        get :delete2,  on: :member
     end
  end

ルーティングを確認すると,以下のように delete2_todo_list (GET メソッド) が作成されていることがわかる.

$ rails routes | grep delete2

    delete2_todo_list   GET    /todo_lists/:id/delete2(.:format)    todo_lists#delete2

コントローラ

ルーティングで設定した delete2 アクションをコントローラで設定する必要がある. before_action で新規作成したアクション (delete2) を指定しておかないとビューから利用できない. また,新たに delete2 アクションを定義しておく.なお,delete2 アクションの中身は 既に設定されている destroy とほとんど同じにしている.

$ vi app/controllers/todo_lists_controller.rb

   以下を適当な場所に入れる.ActiveRecord の destroy メソッドで当該データを削除する.

  class TodoListsController < ApplicationController
     before_action :set_todo_list, only: %i[ show edit update destroy delete2 ]

     ...(中略)...

     # GET /todo_lists/1/delete2
     def delete2
        @todo_list.destroy
        redirect_to todo_lists_url, notice: "Todo を削除しました"
     end

     ...(後略)...

ビュー

インデックスページに「削除」のリンクを作るには,以下のようなソースを 追加すれば良い (scaffold で作られた元々の show.html.erb を参考にすると良い).

<%= link_to '編集', edit_todo_list_path(todo_list)%>
<%= link_to '削除', delete2_todo_list_path(todo_list) %>

各ページの装飾には CSS を使う. app/assets/stylesheets/application.css に定義を書けば良い. 先に挙げたページを作るための CSS の一部は以下の通りであるが, 各自好みの見た目となるよう CSS を定義すること (以下と全く同じにしないこと).

$ vi app/assets/stylesheets/application.css

  table {
     width:100%;
     text-align: left;
     border-collapse: collapse;
  }
  th {
     padding: 10px;
     border-bottom: solid 5px #748ca5;
  }
  td {
     padding: 10px;
     border-bottom: solid 1px #748ca5;
  }
  div {
     padding: 0.5em 1em;
     margin: 2em 0;
     font-weight: bold;
     color: #6091d3;/*文字色*/
     background: #FFF;
     border: solid 3px #6091d3;/*線*/
     border-radius: 10px;/*角の丸み*/
  }
  ...(略)...

課題

内容

例題で完成させたアプリをベースに,TODO の期日や完了の機能を実装する.

アプリ (/todo_lists/) の見た目を以下のように変更し,全てのページが動作するようにしなさい.ビュー・コントローラ・ルーティングのファイル,およびページのスクリーンショット (4 枚,含む URL バー) を提出すること.

  • データベースに,終了したか否かを示すカラムと期日のカラムを追加する.
    • カラム名は,それぞれ completed と deadline とする.
      • completed の型は boolean, deadline の型は date とする.
  • 「インデックスページ」を以下のようにすること
    • まだ完了していないもの (todo_lists テーブルのカラム completed が nil や空白なもの) だけを表示する.
    • 表中に期日 (todo_lists テーブルのカラム deadline) も表示する.
    • 操作のところに「完了」のリンクを追加する.それをクリックすると,データベースの completed カラムに 1 ないし true が入力され,「インデックスページ」にリダイレクトされること.
      • 完了操作を行った項目がインデックスページで表示されなくなることを確認すること.
    • TODO の各項目のうち,期日まで 7 日以内のものについて背景を「緑」にする.
    • TODO の各項目のうち,期日を過ぎたものについて背景を「赤」にする.
    • 期日が古いものから新しいものとなるよう,データをソートして並べること.

  • 「詳細表示ページ」は以下のような見た目にすること.
    • 期日を表示するようにすること.

  • 「編集ページ」は以下のような見た目にすること.
    • 期日を設定できるようにすること.

  • 「新規作成ページ」は以下のような見た目にすること.
    • 期日を設定できるようにすること.

  • 自分なりの工夫をこらした場合は,評価点を増やします.

考え方

モデル

データベースの todo_lists テーブルにカラムを追加する必要がある. 追加の仕方は,データベースを使ったアプリ作成 2 を参照すること.

$ rails g migration AddCompletedToTodo_lists completed:boolean
$ rails g migration AddDeadlineToTodo_lists deadline:date
$ rails db:migrate

ルーティング

例題の delete2 を参考に,config/routes.rb に complete へのルートを設定する.

resources :todo_lists do
   get :complete, on: :member
   get :delete2,  on: :member
end

コントローラ

コントローラ (app/controllers/todo_lists_controller.rb) に Todo を完了するためのアクション (complete) を実装する.

  • before_action や StrongParameter の設定も忘れないこと.

    before_action :set_todo_list, only: %i[ show edit update destroy delete2 complete ]
    
    ...
    
    # GET /todo_lists
    def index
       # @todo_lists = TodoList.all                                         # これは全件検索
       @todo_lists = TodoList.where(completed: [nil, '']).order(:deadline)  # 検索条件付けて,降順に並べる
    end                                                                      # ActiveRecord の書式
    
    ...
    
    # GET /todo_lists/1/complete
    def complete
       @todo_list.update(completed: true)                           # データベースのカラムの値をアップデート
       redirect_to todo_lists_url, notice: "Todo を完了しました"    # ActiveRecord の書式
    end
    
    ...
    
     def todo_list_params
         params.require(:todo_list).permit(:title, :content, :deadline)
     end

ビュー

期日までの日数に応じて背景色を変える操作はビューで行えば良い. Ruby で現在時刻を取得し (Date.current),その値と todo_list.deadline の値を比較する.

<tbody>
   <% @todo_lists.each do |todo_list| %>
   <% current_date = Date.current
      if todo_list.deadline
         if (todo_list.deadline <= current_date)
             bgcolor = "pink"
         elsif (todo_list.deadline <= current_date + 7)
             bgcolor = "lightgreen"
         else
             bgcolor = "white"
         end
      end
   %>
   <tr bgcolor=<%=bgcolor%>>

 ...(略)...

作成や編集画面で期日を設定するためには,form.date_field を使うと良い.

<%= form.label :deadline, style: "display: block" %>
<%= form.date_field :deadline %>