[Ruby on Rails] 単一モデルのCRUD 

# この記事を書くきっかけ
最近railsへの理解を深めるためにtetatailで質問の回答を始めました。
そこで質問を見ていると何人かCRUDの実装をする際に基礎的な知識が抜けているなと感じることがあり、まるで約一年前の自分をみているようだったので何か役に立てたらいいなと思いこの記事を書きました。
[CRUDとは](https://wa3.i-3-i.info/word123.html)
またこの一つのCRUDについて理解することで2個3個とモデルが増えた時もこの基礎的な知識を元に応用していく感じなのでまず1つのモデルのCRUDについてしっかり学ぶのもいいと思います。

# 対象読者
- ルーティングについて学習したい方
- `params[:id]`が何を指すのかわからない方
- 約一年前の自分

# 概要
1つのモデル(モデル名: Post, カラム: title, content)のCRUD機能をできるだけ詳しく説明しています。
モデルについての説明はこの記事ではほとんど触れません。
ルーティングとコントローラーがメインになります。
ちなみにしつこいぐらいルーティングは登場します。
またコードも普段ならまとめる箇所もあえてそのままの記述にしているのでその点はご容赦ください。

## この記事の流れ
1.新規プロジェクト作成
2.Postモデルの作成
3.ルーティング設定
4.PostsController作成
5.indexアクション定義(中身の記述なし)
6.newアクション定義、新規登録フォーム作成
7.createアクション定義
6.indexアクション中身記述
7.showアクション定義、詳細ページ作成
8.editアクション定義、編集フォーム作成
9.updateアクション定義
10.destroyアクション定義
の順に説明していきます。

## 環境
```
% ruby -v
ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin19]

% rails -v
Rails 6.0.3.2

% postgres --version
postgres (PostgreSQL) 9.6.18
```

# 1.新規プロジェクト作成

rails new コマンドでアプリを作成します。今回アプリ名は`one_model_crud`にします。

```
rails new [アプリ名](one_model_crud) -d postgresql
```

database.ymlを編集します


```database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  username: <%= ENV['PG_USERNAME'] %> # postgresqlで設定したusername
  password: <%= ENV['PG_PASSWORD'] %> # postgresqlで設定したpassword
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
```

```
% rails db:create
Created database 'one_model_crud_development'
Created database 'one_model_crud_test'

% rails s
```

http://localhost:3000/ にアクセスします。
![スクリーンショット 2020-08-15 9.46.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524516/a027268f-5653-0784-9619-14693fece4fd.png)

本題とはずれるのですが今回viewはslim、スタイルを整えるためにbootstrap、デバックツールとしてpry-railsを使用しています。

[slim変換](https://qiita.com/hmuronaka/items/f0e4da231ce97da49043)

[bootstrap導入](https://qiita.com/kazutosato/items/d47b7705ee545de4cb1a)

これで準備は整いました。

# 2.Postモデルの作成
rails g model モデル名(単数形)でモデルを作成します。

```
% rails g model post
```

titleカラムとcontentカラムを追加します。

```XXXX_create_posts.rb
class CreatePosts < ActiveRecord::Migration[6.0]
  def change
    create_table :posts do |t|
      t.string :title
      t.text   :content

      t.timestamps
    end
  end
end

```
作成したデータベース情報を反映させます。

```
% rails db:migrate
== 20200815005104 CreatePosts: migrating ======================================
-- create_table(:posts)
   -> 0.0271s
== 20200815005104 CreatePosts: migrated (0.0272s) =============================
```

コンソールで確認してみます。

```console
% rails c
irb(main):001:0> Post.new
=> #<Post id: nil, title: nil, content: nil, created_at: nil, updated_at: nil>
irb(main):002:0> Post.new(title: 'タイトル1', content: 'コンテンツ1').save
   (0.3ms)  BEGIN
  Post Create (7.9ms)  INSERT INTO "posts" ("title", "content", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["title", "タイトル1"], ["content", "コンテンツ1"], ["created_at", "2020-08-15 01:03:24.246072"], ["updated_at", "2020-08-15 01:03:24.246072"]]
   (6.8ms)  COMMIT
=> true
```
Postモデルを作成できました。
次はこれをブラウザでできるようにします。

# 3.ルーティング設定
http://localhost:3000/ にアクセスした時に`posts#index`にアクセスするようにルーティングを設定します。

```routes.rb
Rails.application.routes.draw do
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  root to: "posts#index"
  resources :posts
end
```
`resources :モデル(複数形)`とすることでそのモデルの基本的な7つのアクション index, new, create, show, edit, update, destroyのルーティングが生成されます。

また`resources :モデル(複数形), only: :index`や`resources :モデル(複数形), only: %i[index]` と記述することでアクションを限定することもできます。

では生成されたルーティングをコンソールで見てみます。

```console
% rails routes
     Prefix Verb       URI Pattern               Controller#Action
       root  GET      /                           posts#index
       posts GET      /posts(.:format)            posts#index
             POST     /posts(.:format)            posts#create
    new_post GET      /posts/new(.:format)        posts#new
   edit_post GET      /posts/:id/edit(.:format)   posts#edit
        post GET      /posts/:id(.:format)        posts#show
             PATCH    /posts/:id(.:format)        posts#update
             PUT      /posts/:id(.:format)        posts#update
             DELETE   /posts/:id(.:format)        posts#destroy

```

上記のようなルーティングが作成されました。

- `Prefix` は後で説明します。
- `Verb` はHTTPメソッドになります。
- `URI Pattern` は http://localhost:3000 以降のURLを指しています。
- `Controller#Action` は URLにアクセスした時にどのコントローラのどのアクションを実行するかを指します。

それを踏まえた上で http://localhost:3000/ を見てみましょう。

- `prefix` **root**
- `Verb` **GET**
- `URI Pattern` **/** 
- `Controller#Action` **posts#index** 

これはhttp://localhost:3000/ にアクセスした時に postsコントローラーのindexアクションを実行するという意味になります。  

# 4.PostsController作成
`rails g controller モデル(複数形)` でコントローラを作成します。

```console
% rails g controller posts
Running via Spring preloader in process 31151
      create  app/controllers/posts_controller.rb
      invoke  erb
      create    app/views/posts
      invoke  test_unit
      create    test/controllers/posts_controller_test.rb
      invoke  helper
      create    app/helpers/posts_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/posts.scss
```
`app/controllers/posts_controller.rb`と`app/views/posts`以外使用しないので削除しても大丈夫です

# 5.indexアクション定義(中身の記述なし)

```PostsController.rb
class PostsController < ApplicationController
  def index
  end
end
```
中身の設定はPostを作成できるようになった後で設定します。



app/views/posts/にindex.slimを作成し以下を記述

```index.slim
h1 Posts#index
```

これで準備は整いました。では実際に http://localhost:3000/ にアクセスします

```console
% rails s
```
アクセスした時の流れ
1. PostsControllerのindexアクションを実行
2. views/posts/index.slimを表示
となります。

実際にアクセスすると下記のような画面が表示されます。

![スクリーンショット 2020-08-15 11.12.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524516/38df4182-17a8-0941-3691-4aea9c36cc54.png)

# 6.newアクション定義、新規登録フォーム作成

実装の流れとしては
1.リンクの設置
2.PostsControllerのnewアクション
3.new.slimに遷移
4.PostsControllerのcreateアクション
のようになります。


### 1.リンクの設置
まず新規登録画面にアクセスできるリンクを作成します。ここで必要なのが**Prefix**です。
リンクは下記のような形を記述することで作成できます。
`= link_to '表示する文字', Prefix_path`
このように記述することでPrefix_pathのURI Patternにアクセスするリンクを生成することができます。
実際に記述します。

```index.slim
h1 Posts#index
= link_to '新規登録', new_post_path
```
このリンクは`Prefix`が**new_post**の `URI Pattertn`に遷移するリンクになります。
newアクションのルーティングを見てみます。

```
Prefix Verb       URI Pattern               Controller#Action
new_post GET      /posts/new(.:format)        posts#new
```
これで新規登録画面にアクセスするリンクを作成できました。
![スクリーンショット 2020-08-15 11.42.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524516/71d6da0f-797c-6332-85af-62e1a5666ac0.png)


新規登録のリンクをクリックするとURLが/posts/newに変更します。(*まだnewアクションを定義していないのでエラーが表示されます)

![スクリーンショット 2020-08-15 11.43.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524516/f4dd3986-aa6d-3ad2-1a9b-63df27e0d113.png)

### 2.PostsControllerのnewアクション

```PostsController.rb
def new
  @post = Post.new
end
```

Post.newでPostモデルのインスタンスを生成しそのインスタンスを@postに代入しています。

### 3.new.slimに遷移

```new.slim
.container
  h1.mt-4 新規登録
  = form_with url: posts_path, model: @post do |form|
    .form-group
      = form.label :title, 'タイトル'
      = form.text_field :title, class: 'form-control'
    .form-group
      = form.label :content, '内容'
      = form.text_area :content, class: 'form-control'
    = form.submit '登録', class: 'btn btn-primary'

```


form_withについてはこちら => [form_with使い方](https://pikawaka.com/rails/form_with#form_with%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9)

簡単に説明すると
= form_with posts_path で submitを押した時の遷移先を設定できます。

ここでcreateアクションのルーティングを見て欲しいのですが

```
Prefix Verb       URI Pattern               Controller#Action
posts GET      /posts(.:format)            posts#index
      POST     /posts(.:format)            posts#create
```

Prefixに何も記述されていない場合は上を辿っていき初めに存在するPrefixになります。
今回の場合だとPrefixはpostsにあたります。
なのでposts_pathはposts#indexとposts#createの2種類あるということになります。
この2つをどう判断しているのかというとVerb(**HTTPメソッド**)です。
form_withはデフォルトのHTTPメソッドはPOSTに設定されています。
ということはnew.slimに書いた**posts_path**は`PostsControllerのcreateアクションを実行する`という意味になります。


### 4.PostsControllerのcreateアクション
まずどのようなパラメータが送られてくるのかを確認します。

```PostsController.rb
def create
  binding.pry
end
```
と記述してどのような形でデータが送られてくるのかをコンソールで見てみます。

```console
 9: def create
 => 10:   binding.pry
    11: end

[1] pry(#<PostsController>)> params
=> <ActionController::Parameters {"authenticity_token"=>"d9j/87bg84JqMg5cPr7HwMKi8PIbw8gmEhMj4EvgIblmRKqdmGAdmk1THcW9095M2cBdQxGigM/PZ+VYl9ZGYA==", "post"=>{"title"=>"タイトル2", "content"=>"コンテンツ2"}, "commit"=>"登録", "controller"=>"posts", "action"=>"create"} permitted: false>
[2] pry(#<PostsController>)> params[:post][:title]
=> "タイトル1"
[3] pry(#<PostsController>)> params[:post][:content]
=> "コンテンツ2"
```
このように記述することで中身を確認することができました。あとはこれを保存します。

```PostsController.rb
def create
  @post = Post.new(title: params[:post][:title], content: params[:post][:content])
  @post.save
end
```
とすると一応保存はできます。
しかし今回は不正なパラメータを防ぐストロングパラメータという仕組みを使い保存します。

```PostsController.rb
def create
  @post = Post.new
  @post.save(post_params)
  redirect_to posts_path
end

private

def post_params
  params.require(:post).permit(:title, :content)
end
```
修正後のコードはこのようになります。今回はtitleとcontentが空でも保存できるようになっているのでif文で条件分岐のコードは記述していません。
createアクションの最後の記述 `redirect_to posts_path` でurlが `/` に遷移すなわちPostsControllerのindexアクションを実行するようにしています。

### 6.indexアクション中身記述
createアクションにてデータの生成ができたので次はそのデータを取得して表示します。

```PostsController.rb
def index
 @posts = Post.all
end
```
モデル.allでそのモデルのデータ全てを取得することができその取得したデータを@postsに代入しています。
@postsには複数のデータが入ることが想定されるので複数形になっています。ちなみにデータは以下のような形で入っています。

```console
[#<Post:0x00007f94bc4d83b8 id: 1, title: "タイトル1", content: "コンテンツ1", created_at: Sat, 15 Aug 2020 05:19:09 UTC +00:00, updated_at: Sat, 15 Aug 2020 05:19:09 UTC +00:00>,
 #<Post:0x00007f94bc4ae838 id: 2, title: "タイトル2", content: "コンテンツ2", created_at: Sat, 15 Aug 2020 05:19:22 UTC +00:00, updated_at: Sat, 15 Aug 2020 05:19:22 UTC +00:00>]
```
ではこれらを表示してみます。

```index.slim
.container
  h1.mt-4 Posts#index
  = link_to '新規登録', new_post_path

  // 追記
  - @posts.each do |post|
    .card.my-4
      .card-header
        = post.title
      .card-body
        p.card-text
          = post.content

```
@postsはindexアクションで定義したものです。
それをeachメソッドで1つずつ取り出し、取り出した1つのデータがpostに入ります。
= post.title, = post.contentでpostデータのtitleカラム、contentカラムを参照しています。
画面は下記のようになります。
![スクリーンショット 2020-08-15 14.28.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524516/77b80502-0910-d48b-ba8c-2231f3fb910b.png)

# 7.showアクション定義、詳細ページ作成

まず、それぞれのデータの下に詳細画面に遷移するボタンを設置します。

```index.slim
.container
  h1.mt-4 Posts#index
  = link_to '新規登録', new_post_path

  - @posts.each do |post|
    .card.my-4
      .card-header
        = post.title
      .card-body
        p.card-text
          = post.content
        // 追記
        = link_to '詳細', post_path(post), class: 'btn btn-success mr-2'
```
post_pathの**引数にpostを取っている**点に注目してください。
showアクションのルーティングを見てみます。

```console
Prefix Verb       URI Pattern               Controller#Action
    post GET      /posts/:id(.:format)        posts#show
```
URI Patternに:idがありこの値は動的に変更されます。（例: `/posts/1`, `/posts/7`）
post_pathのみだとidが何かわからないため引数をとります。(今回はpost)
こうすることでpostのidを取得することができ`/posts/:id`に遷移することができます。
ではshowアクションにどのようなパラメータが送られるのかみてみます。

```PostsController.rb
def show
  binding.pry
end
```
binding.pryで止めてコンソールを確認

```console
pry(#<PostsController>)> params
=> <ActionController::Parameters {"controller"=>"posts", "action"=>"show", "id"=>"1"} permitted: false>
[2] pry(#<PostsController>)> params[:id]
=> "1"
```
このように`params[:id]`と記述することでidが取得できます。
このidを用いてPostモデルのデータの中からデータを一つ取得します。

```PostsController.rb
def show
  @post = Post.find(params[:id])
end
```
モデル.find(id番号)とすることでモデルのidとid番号が一致するデータを一つ取得します。
続いてshow.slimです。

```show.slim
.container
  .card.my-4
    .card-header
      = @post.title
    .card-body
      p.card-text
        = @post.content
```
遷移するとこのような画面が表示されます。
![スクリーンショット 2020-08-15 16.39.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/524516/1830f126-fa8f-caa8-1bff-a4dcefaf3a5c.png)

# 8.editアクション定義、編集フォーム作成
まず詳細ボタンと同じよう編集ボタンを設置します。

```index.slim
.container
  h1.mt-4 Posts#index
  = link_to '新規登録', new_post_path

  - @posts.each do |post|
    .card.my-4
      .card-header
        = post.title
      .card-body
        p.card-text
          = post.content
        = link_to '詳細', post_path(post), class: 'btn btn-success mr-2'
        // 追記
        = link_to '編集', edit_post_path(post), class: 'btn btn-primary mr-2'

```
editアクションのルーティングを見てみます。

```console
Prefix Verb       URI Pattern               Controller#Action
edit_post GET      /posts/:id/edit(.:format)   posts#edit
```
続いてeditアクションを定義します。

```PostsController.rb
def edit
  @post = Post.find(params[:id])
end
```
showアクションと同じ記述になります。
続いてedit.slimを記述します。


```edit.slim
.container
  h1.mt-4 編集
  = form_with url: post_path(@post), model: @post do |form|
    .form-group
      = form.label :title, 'タイトル'
      = form.text_field :title, class: 'form-control'
    .form-group
      = form.label :content, '内容'
      = form.text_area :content, class: 'form-control'
    = form.submit '更新', class: 'btn btn-primary'

```

new.slimとほとんど同じ内容なので説明は省略します。post_path(@post)はnew.slimで定義した時と同様でこのHTTPメソッドはPATCHになります。一応ルーティングも見ておきます。


```console
Prefix Verb       URI Pattern               Controller#Action
   post GET      /posts/:id(.:format)        posts#show
        PATCH    /posts/:id(.:format)        posts#update
        PUT      /posts/:id(.:format)        posts#update
```
HTTPメソッドがPATCHなので今回の更新ボタンが押された時に実行するアクションは`PostsControllerのupdateアクション`になります。


# 9.updateアクション定義

updateアクションを定義します。

```PostsController.rb
def update
  @post = Post.find(params[:id])
  @post.update(post_params)
  redirect_to posts_path
end
```
こちらはcreateアクションとほとんど同じ内容になります。
createアクションとの違いはモデルをnewするかfindするかの違いです。

# 10.destroyアクション定義
最後にデータを削除します。
編集ボタン同様削除ボタンを設置します。destroyアクションのルーティングを見てみます。

```console
Prefix Verb       URI Pattern               Controller#Action
　post GET      /posts/:id(.:format)        posts#show
       DELETE   /posts/:id(.:format)        posts#destroy
```
同じpost_pathなのでHTTPメソッドで区別します。 `method: :HTTPメソッド`とすることで指定したパスの指定したHTTPメソッドのアクションを実行する形になります。実際に記述します。

```index.slim
.container
  h1.mt-4 Posts#index
  = link_to '新規登録', new_post_path

  - @posts.each do |post|
    .card.my-4
      .card-header
        = post.title
      .card-body
        p.card-text
          = post.content
        = link_to '詳細', post_path(post), class: 'btn btn-success mr-2'
        = link_to '編集', edit_post_path(post), class: 'btn btn-primary mr-2'
        // 追記
        = link_to '削除', post_path(post), method: :delete, class: 'btn btn-danger'
```

続いてdestroyアクションを定義します。

```PostsController.rb
def destroy
  @post = Post.find(params[:id])
  @post.destroy
  redirect_to posts_path
end
```

これで1つのモデルのCRUDが実装できました。

# 完成形のコード
一応完成形のコードを下記に記載しておきます。

```Postscontroller.rb
class PostsController < ApplicationController
  def index
    @posts = Post.all
  end

  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)
    @post.save
    redirect_to posts_path
  end

  def show
    @post = Post.find(params[:id])
  end

  def edit
    @post = Post.find(params[:id])
  end

  def update
    @post = Post.find(params[:id])
    @post.update(post_params)
    redirect_to posts_path
  end

  def destroy
    @post = Post.find(params[:id])
    @post.destroy
    redirect_to posts_path
  end

  private

  def post_params
    params.require(:post).permit(:title, :content)
  end
end
```

# おわりに
とりあえずこれで1年前の自分に見せたい内容は一通り書くことができました。