Railsでスケジュール管理アプリを作成してみる

# はじめに
Railsを用いたスケジュール管理アプリを作成していきます。

※Rails学習の忘備録のため誤っている箇所があるかもしれません。ご了承ください。

# 環境
MacOS Sonoma 14.0
Ruby 3.0.4p208
Rails 6.1.3.2

## 要件
１　以下のページが表示される
（１）予定一覧ページ
　　・予定合計及び本日の日付が表示される
　　・詳細、編集、削除のリンク
　　・削除リンククリック時に確認メッセージ
（２）予定の新規追加ページ（バリデーション機能の実装）
　　・タイトルは２０文字以内
　　・終了日は開始日より後の日付とする
　　・メモは５００文字以内
（３）予定詳細ページ
（４）予定編集ページ（バリデーション機能の実装）
２　日付は日本時間とする
３　スケジュールの作成、編集に成功／失敗した場合はフラッシュメッセージを表示する
４　Railsのバージョンは6.1.3.2とする。

# 作成例
### 予定一覧
![スクリーンショット 2023-11-11 15.51.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586882/5582c5e8-d193-6bd6-7907-ad2eff45820a.png)

### 新規追加
![スクリーンショット 2023-11-11 15.52.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586882/a966fd9c-f879-2d51-fac2-3e1333014a08.png)

### 予定詳細
![スクリーンショット 2023-11-11 15.52.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586882/6fbb334e-f1dd-5083-90cb-3c1db77759c9.png)

### 予定編集
![スクリーンショット 2023-11-11 15.52.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586882/9d401a5d-ce75-3e10-b85d-dda70914ca3d.png)

### 予定削除
![スクリーンショット 2023-11-11 15.52.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586882/c7eb1dc4-1705-f485-ca80-21b88474ec36.png)


# アプリケーション作成

## Railsバージョンの指定
要件でRailsのバージョンが指定されているため、バージョンを指定を行う。
```
$ rails _6.1.3.2_ new schedule --skip-bundle
```

## Gemfileを編集
~/schedule内のGemfileを編集
```Ruby:Gemfile（変更前）
gem 'rails', '~> 6.1.3', '>= 6.1.3.2'
```

```Ruby:Gemfile（変更後）
gem 'rails', '= 6.1.3.2'
```
## bundle installを実施
~/scheduleで以下のコマンドを実施
```Ruby:bundle install
$ bundle install
```

## Webpackerインストール
Rails6以降ではWebpackerインストールが必須になったため、インストール
```Ruby:Webpackerインストール
$ rails webpacker:install
```


## Rubyバージョンの確認
rails -vを実施して指定したバージョンになっているか確認。
```Ruby:バージョン確認
$ rails -v
```
Rails 6.1.3.2と表示されれば成功。
`rails s`でサーバーが無事起動するか確認。
```Ruby:サーバー起動
$ rails s
```
http://localhost:3000/ にアクセスし、Rubyのバージョンを確認。

![スクリーンショット 2023-11-01 14.24.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586882/640bb853-8c7a-2e65-65d6-51e1fb1e5674.png)

# CRUD処理と７つのアクションとは
CRUDはそれぞれ以下を示す。
|CRUD|意味|アクション|用途|httpメソッド|
|---|---|---|---|---|
|Create|作成|create |DBの登録処理|post|
|Read|読み取り|new   |新規作成ページの表示|get|
|Read|読み取り|index |一覧を表示|get|
|Read|読み取り|show  |詳細を表示|get|
|Read|読み取り|edit   |編集ページを表示|get|
|Update|更新|update |DBの更新処理|patch|
|Delete|削除|destroy|DBの削除処理|delete|

# 各ページの役割
|ページ|役割|アクション|
|---|---|---|
|トップページ|スケジュールの全体表示 |index|
|新規作成  |スケジュール作成ページの表示、作成 |new,create|
|詳細  |スケジュールの個別表示 |show|
|編集  |スケジュールの編集ページ表示、更新  |edit,update|
|削除  |スケジュールの削除 |destroy|

## Controller作成
`rails generate controller <コントローラー名> <アクション名>`でコントローラを作成する。
コントローラー名は複数形、頭文字は大文字
```
$ rails generate controller Tasks index
```

## tasks_controller.rbを編集（CRUD処理の実装）
~/schedule/app/controllers/tasks_controller.rbを編集する。

```Ruby: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
      flash[:notice_create] = "スケジュールを登録しました"
      redirect_to :tasks
    else
      flash[:notice_no_create] = "スケジュールの登録に失敗しました"
      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)
      flash[:notice_update] = "スケジュールを更新しました"
      redirect_to :tasks
    else
      flash[:notice_no_update] = "スケジュールを更新できませんでした"
      render "edit"
    end
  end

  def destroy
    @task = Task.find(params[:id])
    @task.destroy
    flash[:notice_destroy] = "スケジュールを削除しました"
    redirect_to :tasks
  end

  private
  def task_params  # プライベートメソッド 
    params.require(:task).permit(:title, :start_date, :end_date, :is_all_day, :memo)
  end

end
```
https://qiita.com/iverson3kobe0824/items/41b50f010c7f6b78485d


## ルートファイルを編集
~/schedule/config/routes.rbを編集する。
```Ruby:routes.rb
Rails.application.routes.draw do
# resources :tasksと重複するため、以下の２行をコメントアウト
  # get 'tasks/index'
  # get 'tasks/new'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
# 以下を追記
  resources :tasks # ルーティングをまとめて設定
  root to: 'tasks#index' # トップページの設定
end
```
https://qiita.com/Kooo-X/items/dfde05200b3a0332c5ee

https://qiita.com/nao0725/items/fab88f8779712fded3e2

routes（ラウツと発音するらしい）の確認
```
$ rails routes
```
以下のように表示されていれば問題なし。
|Prefix|Verb|URI Pattern|Controller#Action|
|---|---|---|---|
|tasks_index|GET|/tasks/index(.:format) |tasks#index|
|tasks_new|GET|/tasks/new(.:format)   |tasks#new|
|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|

# モデル作成
```
$ rails g model Task title:string start_date:date end_date:date is_all_day:boolean memo:string
```
~/schedule/app/dbに以下のファイルが作成される。
```yyyymmddHHMMSS_create_tasks.rb
class CreateTasks < ActiveRecord::Migration[6.1]
  def change
    create_table :tasks do |t|
      t.string :title
      t.date :start_date
      t.date :end_date
      t.boolean :is_all_day
      t.string :memo

      t.timestamps
    end
  end
end

```
マイグレーションファイルからテーブルを作成する。
```
rails db:migrate
```
```
-- create_table(:tasks)
   -> 0.0018s
```
が表示されれば成功

rails dbconsoleを実施し、tableが作成されているか確認
```
$ rails dbconsole
```

```
$ .tables
```
tasksが表示されていることを確認。

```
$ .schema tasks
```
以下のように表示されれば問題なし。
```
CREATE TABLE IF NOT EXISTS "tasks" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar, "start_date" date, "end_date" date, "is_all_day" boolean, "memo" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);
```

# レイアウトの作成
## ヘッダーの作成（パーシャル）
~/schedule/app/views/にsharedフォルダを作成し、_partial.html.erbファイルを新規作成
```_partial.html.erb
<div class="partial">
  <h4>スケジュール管理アプリ</h4>
</div>
```

## スタイルシートの作成
~/schedule/app/assets/stylesheets/内にcommon.scssを作成
```common.scss
header {
    width: 100%;
    padding: 15px;
    margin-bottom: 30px;
    border-bottom: 7px solid orange;
  }
  
  footer{
    width: 100%;
    padding: 15px;
    margin-top: 30px;
    border-top: 7px solid orange;
  }
  
  .partial {
    color: white;
    background-color: black;
    text-align: center;
    padding: 1px;
  }

  table th {
    padding: 10px 20px;
    border-bottom: 2px solid gray;
  }
  
  table td {
    background-color: #f9ecd5;
    padding: 10px 20px;
  }
  
  .notice {
    background-color: #ccf;
    padding: 5px;
  }
```

## 予定一覧ページ
~/schedule/app/views/tasks/index.html.erb
```index.html.erb
<h3>予定一覧</h3>

<table>
  <thead>
    <tr>
      <th>id</th>
      <th>タイトル</th>
      <th>開始日</th>
      <th>終了日</th>
      <th>終日</th>
      <th>更新日時</th>
      <th></th>
      <th></th>
      <th></th>
    </tr>
  </thead>

  <tbody>
    <% @tasks.each do |task| %>
      <tr>
        <td><%= task.id %></td>
        <td><%= task.title %></td>
        <td><%= task.start_datetime %></td>
        <td><%= task.end_datetime %></td>
        <td><%= task.is_all_day %></td>
        <td><%= task.updated_at %></td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
    <% end %>
  </tbody>
</table>

<p><%= link_to "予定の新規登録", :new_task %></p>
```

## 予定の新規登録ページ
~/schedule/app/views/tasks/new.html.erb
```new.html.erb
<h3>ユーザー新規作成</h3>

<%= form_with model: @task do |f| %>
  <table>
    <tr>
      <th><%= f.label :title ,"タイトル" %></th>
      <td><%= f.text_field :title %></td>
    </tr>

    <tr>
      <th><%= f.label :start_datetime ,"開始日" %></th>
      <td><%= f.datetime_field :start_datetime %></td>
    </tr>

    <tr>
      <th><%= f.label :end_datetime ,"終了日" %></th>
      <td><%= f.datetime_field :end_datetime %></td>
    </tr>

    <tr>
      <th><%= f.label :is_all_day ,"終日の場合はチェックを入れます" %></th>
      <td><%= f.check_box :is_all_day %></td>
    </tr>

    <tr>
      <th><%= f.label :memo ,"スケジュールメモ" %></th>
      <td><%= f.text_area :memo %></td>
    </tr>
  </table>

  <div>
    <ul>
      <li><%= f.submit "登録完了する"%></li>
      <li><%= link_to "予定一覧に戻る", :tasks %></li>
    </ul>
  </div>
<% end %>
```

## 予定の編集ページ
~/schedule/app/views/tasks/edit.html.erb
```edit.html.erb
 <%= render "shared/partial" %>

<h3>予定の編集</h3>

<%= form_with model: @task do |f| %>
  <table>
    <tr>
      <th><%= f.label :title ,"タイトル" %></th>
      <td><%= f.text_field :title %></td>
    </tr>

    <tr>
      <th><%= f.label :start_date ,"開始日" %></th>
      <td><%= f.date_field :start_date %></td>
    </tr>

    <tr>
      <th><%= f.label :end_date ,"終了日" %></th>
      <td><%= f.date_field :end_date %></td>
    </tr>

    <tr>
      <th><%= f.label :is_all_day ,"終日の場合はチェックを入れます" %></th>
      <td><%= f.check_box :is_all_day %></td>
    </tr>

    <tr>
      <th><%= f.label :memo ,"スケジュールメモ" %></th>
      <td><%= f.text_area :memo %></td>
    </tr>
  </table>

  <div>
    <ul>
      <li><%= f.submit "編集を完了する"%></li>
      <li><%= link_to "キャンセル", :tasks %></li>
    </ul>
  </div>

 <% if flash[:notice_no_update] %>
   <p class="notice"><%= flash[:notice_no_update] %></p>
 <% end %>

 <%= render "shared/error", obj: @task %> 
<% end %>
```

## 予定の詳細ページ
~/schedule/app/views/tasks/show.html.erb
```show.html.erb
 <%= render "shared/partial" %>

<%= link_to "一覧に戻る", :tasks %>

<%= @task.title %>
<%= form_with model: @task do |f| %>
  <table>
    <tr>
      <th>開始日</th>
      <td><%= l @task.start_date %></td>
    </tr>

    <tr>
      <th>終了日</th>
      <td><%= l @task.end_date %></td>
    </tr>

    <tr>
      <th>終日</th>
      <td><%= @task.is_all_day ? '◯': '' %></td>
    </tr>

    <tr>
      <th>スケジュールメモ</th>
      <td><%= @task.memo %></td>
    </tr>
  </table>

<% end %>
```

# 確認
`$ rails s`を実行し、ブラウザで`http://lovalhost:30000`にアクセスし、予定一覧の画面が表示されれば成功。
![スクリーンショット 2023-11-11 15.51.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3586882/d856d337-9bca-af35-fafc-d4b04cba3321.png)

# まとめ
環境構築が一番の難関だったりします。
問題解決能力がエンジニアに必要とされるスキルの一つと学びました。

# 参考リンク
https://qiita.com/soraa24926/items/1d051389792fe4c79fd6

https://qiita.com/okamoto_ryo/items/6bcc49cc0cbcb55f93da