【Ruby on Rails】Todoアプリ作ってみた！

# 【Ruby on Rails】Todoアプリ作ってみた！

## はじめに
この記事は初学者向けでRailsを立ち上げることができ、Railsで何か簡単に作成してみたいという方向けになります。
また自身も学習中のため、備忘録としても残しておきたいと思います。

## 実行環境
M1 mac
Rails version	7.0.5
Ruby version	ruby 3.1.2
RubyGems version	3.3.7
MySQL２

## 完成品
先に完成したコードをご確認ください。
とりあえず動くものを作りたい方は、ブラウザでlocalhost3000でRailsの画面表示できるところまで進めます。
```
rails g controller todos
```
その後、上記入力しコントローラー作成します。todosの部分はコントローラー名になりますので自由に記述してください。注意点としまして、Railsはファイル名などから推測して様々なことを便利にしてくれます。コントローラーはデータベースとの結びつきがありますので必ず複数形で命名しましょう！
また、todo以降にindexやcreateと記述すると対応したviewとroutesを記述して作成してくれます！
後は下記コードをそれぞれ記述してください！


また、今回データベースはMySQLを使用しています。下記記述でテーブルを作成してください！
今回はtodosテーブルにtext型のtitleというカラムを作成しました！（taskの方が良かったと思っています笑）
RailsではプライマリーキーとしてIDが自動設定されますのでIDの記述は必要ありません。また、timestampsというカラムも同時に作成され、このカラムにはそのデータの作成日・作成時間と更新日・更新時間の２つのカラムが作成されます！
※Railsの画面ブラウザで確認済として進めるのでDB作成済としてます。
```
rails g model テーブル名 カラム名:型
```
```ruby:todos_controller.rb
class TodosController < ApplicationController
  def index
    @todos = Todo.all
  end

  def new
    @todo = Todo.new
  end

  def create
    @todo = Todo.create(todo_params)
    @todo.save
    redirect_to todos_path
  end

  def edit
    @todo = Todo.find(params[:id])
  end

  def update
    @todo = Todo.find(params[:id])
    @todo.update(todo_params)
    redirect_to todos_path
  end

  def destroy
    @todo = Todo.find(params[:id])
    @todo.destroy
    redirect_to todos_path
  end

  private
    def todo_params
      params.require(:todo).permit(:title)
    end

end

```

```erb:index.html.erb
<div class="container">
  <h1>Todo List</h1>

  <%= link_to "タスクを追加する", new_todo_path, class:"new-task"%>

  <table>
    <thead>
      <tr>
        <th>タスク</th>
        <th>アクション</th>
      </tr>
    </thead>
    <tbody>
      <% @todos.each do |todo| %>
        <tr>
          <td><%= todo.title %></td>
          <td>
            <%= link_to "編集", edit_todo_path(todo), class: "edit" %>
            <%= link_to "削除", todo_path(todo), data: { turbo_method: :delete, turbo_confirm: '本当に削除していいですか？' }, class: "delete" %>
          </td>
        </tr>
      <% end %>
    </tbody>
  </table>
</div>

```

```erb:new.html.erb
<div class="container">
  <h1>タスクの追加</h1>
  <%= form_with model: @todo, local:true do |form| %>
    <%= form.label :title %>
    <%= form.text_field :title, placeholder: "タスクを入力する" %>
    <input type="submit" value="保存する">
  <% end %>
  <%= link_to '一覧に戻る', todos_path, class: "back" %>
</div>
```

```erb:edit.html.erb
<div class="container">
  <h1>タスクの編集</h1>
  <%= form_with model: @todo, local:true do |form| %>
    <%= form.label :title %>
    <%= form.text_field :title, placeholder: "タスクを入力する" %>
    <input type="submit" value="保存する">
  <% end %>
    <%= link_to '一覧に戻る', todos_path, class: "back" %>
</div>
```
```ruby:routes.rb
Rails.application.routes.draw do
  resources :todos
end
```

```css:application.css
body {
  font-family: "Helvetica Neue", Arial, "Hiragino Kaku Gothic ProN",
    "Hiragino Sans", Meiryo, sans-serif;
  background-color: #f5f5f5;
  padding: 20px;
}

.container {
  max-width: 600px;
  margin: 0 auto;
}

h1 {
  text-align: center;
}

form {
  display: flex;
  margin-bottom: 20px;
}

form input[type="text"] {
  flex: 1;
  padding: 10px;
  margin-right: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

form input[type="submit"] {
  width: 150px;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  color: #fff;
  background-color: #007bff;
  cursor: pointer;
}

table {
  width: 100%;
  border-collapse: collapse;
}

table,
th,
td {
  border: 1px solid #ddd;
}

th,
td {
  padding: 10px;
  text-align: left;
}

.edit,
.delete {
  display: inline-block;
  padding: 10px;
  border: none;
  border-radius: 4px;
  color: #fff;
  text-decoration: none;
  margin-right: 5px;
}

.edit {
  background-color: #ffc107;
}

.delete {
  background-color: #dc3545;
}

.back {
  text-align: center;
  display: inline-block;
  border: none;
  border-radius: 4px;
  color: #fff;
  background-color: #007bff;
  text-decoration: none;
  margin-bottom: 20px;
  padding: 10px 20px;
}

.new-task {
  display: inline-block;
  margin-bottom: 20px;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  color: #fff;
  background-color: #007bff;
  text-decoration: none;
}

```

## 各コードの説明　controller編
全てを細かく説明すると長くなってしまうので要所要所説明いたします！
まず、コントローラーの記述です！
```ruby
def index
  @todos = Todo.all
end
```
上記の様な記述がRailsで多く出ますが、これはインスタンス変数にデータベースの値を取り、そのインスタンス変数に格納している記述になります。
インスタンス変数は簡単に言ってしまうと様々なところから参照できる変数です。
Todoになっているのがテーブルの指定で.allというのがRailsのメソッドになります。
.allだとそのカラムのデータを全て取得するという意味になります！

---
```ruby
def new
    @todo = Todo.new
end

def create
    @todo = Todo.create(todo_params)
    @todo.save
    redirect_to todos_path
end
```
次に上記コードです！
.newメソッドは初期化で新しいデータ作成しますの宣言の様なイメージでRailsではnewアクションにGETリクエスト送りその後POSTリクエストがされるとcreateアクションを自動で呼び出す様になっています！
細かい説明は省きますがイメージはこれで大丈夫かと！
newした後はユーザーにHTMLでデータ記述してもらいPOSTするbuttonが押されるとcreateが呼び出されます！

---
```ruby
@todo = Todo.create(todo_params)
```
上記部分はtodo_paramsを引数として受け取り新しくcreateします！という宣言ですね。なので、このアクションを呼ぶ際には引数を渡す記述が必要になるということをお忘れなく！
```ruby
@todo.save
redirect_to todos_path
```
その後、newで新しく作成したインスタンス変数todoをsaveメソッドでDBに保存する記述になります。
redirect_to todos_pathこの記述は最初のindexアクションに戻るという意味になります。

ここで1つ注意点でindexアクションでは変数名がtodosと複数形なのに対し、ここではtodoと単数型になります。allはテーブルの全てのデータなので複数形で新規作成では１つなので単数型になります。
Railsでは単数・複数の判断も多くなりますので十分に気を付けて記述してください！

```ruby
def edit
  @todo = Todo.find(params[:id])
end
```
ここも先ほどとほぼ同じで引数にidがついている状態です。findは特定のデータを取り出すメソッドになるのでその特定のデータを判別するのにidが必要になります！
残りの記述も同じイメージでメソッドが違うだけなのでドキュメントなどで確認していただければ！

## 各コードの説明　view編
次にHTMLファイルになります！
```erb:index.html.erb
<% @todos.each do |todo| %>
　　<tr>
　　　　<td><%= todo.title %></td>
　　　　<td>
 　　　　<%= link_to "編集", edit_todo_path(todo), class: "edit" %>
　　　　　　<%= link_to "削除", todo_path(todo), data: { turbo_method: :delete,turbo_confirm: '本当に削除していいですか？' }, class: "delete" %>
  </td>
　　</tr>
<% end %>
```
Railsではerbという拡張子でHTMLの中にRubyを記述することができます。
```erb
<% @todos.each do |todo| %>
```
上記のように％で囲った部分がRubyの文になります。
また、次のように<% %>と<%= %>が混在していますが、erbファイルでは<% %>が出力しないRuby文で<%= %>イコールのつくこちらの記述がHTMLに変換し出力する記述になります！
このコードではRubyのeach文でcontrollerのindexの部分で記述した変数をそれぞれ取り出し出力するようなHTMLになります。
```erb
<%= link_to "編集", edit_todo_path(todo), class: "edit" %>
```
上記部分ではedit_todo_path(todo)という風になっており、ここで（todo）と記述しているので、クリックされたtodoのデータを引数として渡すことができます！
この後に先ほど記述したコントローラーにこの値が入り保存などができるという流れです！
ここではeditアクションに飛ぶようにpathが設定されています。

また、上記はクリックされるとHTTPリクエストを飛ばすURLになります。この書き方はRailsをブラウザで立ち上げてURL部分のポート番号（変更していなければ3000）の後に/rails/info/routesと調べると現在作成されているRailsアプリの利用可能なroutes一覧になりますのでご確認ください！（今回はrouteについての記述は省きます）

---
```erb
<%= link_to "削除", todo_path(todo), data: { turbo_method: :delete,turbo_confirm: '本当に削除していいですか？' }, class: "delete" %>
```
ここが1つの詰まりポイントになるかと思うのですが、ここはクリックするとコントローラーに記述したdestroyアクションが実行される部分なのですが、まず、pathの部分ですがここはtodo_pathでコントローラー名の部分は単数型です。todos_pathにするとブラウザでlocalhostの後に/rails/info/routeと打ち込みrouteのpathを見ていただければわかると思うのですが、今回routeの指定はresourceにしているので、複数型ではindexアクションへのpathになります。
単数型にするとHTTPリクエストの種類によって呼び出されるアクションが変わります！
```erb
data: { turbo_method: :delete,　turbo_confirm: '本当に削除していいですか？' }
```
そこで上記記述が必要になります！
ここで一癖あるのですが、バージョンの古いRailsでの記事などでは下記の様に記述されていることあります。
```
method: :delete
```
しかし、Railsバージョン６以上からはturboというライブラリの詰め合わせの様なものが標準装備になっているので、上記記述ではHTTPリクエストがgetのままになってしまうので、showアクションが実行されます。
なので、data: { turbo_method: :delete }という記述でdeleteリクエストになりアクションがdestroyが実行されます！
turbo_confirmはアラームとして小さな確認画面が出てきます！

---
次が新規登録と編集になります。
```erb:new.html.erb
<div class="container">
  <h1>タスクの追加</h1>
  <%= form_with model: @todo, local:true do |form| %>
    <%= form.label :title %>
    <%= form.text_field :title, placeholder: "タスクを入力する" %>
    <input type="submit" value="保存する">
  <% end %>
  <%= link_to '一覧に戻る', todos_path, class: "back" %>
</div>
```

新規登録と編集は呼び出すアクションが違うだけで流れはここまで読んでいただければコードも読めるかと思うので一部割愛します。
上記コードでのポイントはform_withになります。
今までformにも種類がありますが、最新バージョンでの追加で一般的なものがform_withになります。
form_withは様々な指定ができます。ここではmodelという部分でデータベースの指定をしてform.text_fieldの部分で入力値を受け取ります。
その後inputタグのtype="submit"ということでクリックされるとその内容がHTTPのPOSTリクエストとして送信されます。
ここで再度/rails/info/routeでrouteを確認してみて欲しいのですが、2列目にPOSTリクエストだとcreatアクションに繋がるpathがあるかと思います。
routeはリクエストにあうrouteを上から順に探していき最初に見つかったものを実行します！
なのでcreatアクションが呼ばれコントローラーにて記述したsaveメソッドが実行されます。
editも流れは同じで違いは編集のpathは指定されたデータのidを引数に取りそのデータを編集し再度createアクションを実行する流れです！

---
少々長くなってしまったのでここら辺で終わりにします。。。
とりあえずこれでTodoアプリの完成です！

## おわりに
最後までお読みいただきありがとうございます。
まだまだ知見の少ない初学者なのでおかしなところあればコメントにてご指摘いただければと思います！！
ありがとうございました！