[Rails]CRUDアプリを作ってみる 5/20
はじめに
Railsの公式ドキュメントを参考にしながらRailsのCRUDアプリを作っていきます。
環境
Ruby: 3.0.0
Rails: 6.1.7
CRUDアプリとは
CRUDアプリは、データベースやデータストレージとやり取りする基本的な操作である「作成 (Create)」「読み取り (Read)」「更新 (Update)」「削除 (Delete)」を実行するアプリケーションのことを指します。
流れ
1. モデルを作成する
2. ルーティングを作成する
3. コントローラーを作成する
4. ビューを作成する
Articleモデルを作成する
bin/rails generate model Article title:string body:text
invoke active_record
create db/migrate/20230621083727_create_articles.rb
create app/models/article.rb
null制約をかける
class CreateArticles < ActiveRecord::Migration[6.1]
def change
create_table :articles do |t|
t.string :title, null: false
t.text :body
t.timestamps
end
end
end
bin/rails db:migrate
Running via Spring preloader in process 54356
== 20230621083727 CreateArticles: migrating ===================================
-- create_table(:articles)
-> 0.0019s
== 20230621083727 CreateArticles: migrated (0.0021s) ==========================
rails console
で確認する
rails console
でarticleを作成されることを確認します。
$ bin/rails console
Running via Spring preloader in process 54414
Loading development environment (Rails 6.1.7.3)
irb(main):001:0> article = Article.new(title:"hello rails", body:"i am on rails")
(0.8ms) SELECT sqlite_version(*)
=> #<Article id: nil, title: "hello rails", body: "i am on rails", created_at: nil, updated_at: nil>
irb(main):002:0> article.save
TRANSACTION (0.1ms) begin transaction
Article Create (0.8ms) INSERT INTO "articles" ("title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["title", "hello rails"], ["body", "i am on rails"], ["created_at", "2023-06-21 08:44:41.981986"], ["updated_at", "2023-06-21 08:44:41.981986"]]
TRANSACTION (0.5ms) commit transaction
=> true
# titleなしでarticleを作るとエラーが発生する
irb(main):003:0> article = Article.new(body:"i am on rails")
(0.8ms) SELECT sqlite_version(*)
=> #<Article id: nil, title: nil, body: "i am on rails", created_at: nil, updated_at: nil>
irb(main):004:0> article.save
TRANSACTION (0.1ms) begin transaction
Article Create (0.7ms) INSERT INTO "articles" ("body", "created_at", "updated_at") VALUES (?, ?, ?) [["body", "i am on rails"], ["created_at", "2023-06-21 08:45:55.253175"], ["updated_at", "2023-06-21 08:45:55.253175"]]
TRANSACTION (0.1ms) rollback transaction
Traceback (most recent call last):
ActiveRecord::NotNullViolation (SQLite3::ConstraintException: NOT NULL constraint failed: articles.title)
=> false
# エラーメッセージ
irb(main):001:0> article = Article.new
(0.9ms) SELECT sqlite_version(*)
=> #<Article id: nil, title: nil, body: nil, created_at: nil, updated_at: nil>
irb(main):002:0> article.save
=> false
irb(main):003:0> article.valid?
=> false
irb(main):004:0> article.errors.messages
=> {:title=>["can't be blank"]}
concole
では確認できましたので、次はブラウザでも確認していきます。
article.rb
にバリデーションをかける
空のタイトルだとDBに保存されないようにバリデーションが必要です。
class Article < ApplicationRecord
validates :title, presence: true
end
article一覧用のルーティングを作成する
Rails.application.routes.draw do
get "/articles", to: "articles#index"
end
article一覧用のコントローラーを作成する
$ bin/rails generate controller Articles index
Running via Spring preloader in process 54708
create app/controllers/articles_controller.rb
route get 'articles/index'
invoke erb
create app/views/articles
create app/views/articles/index.html.erb
index
アクションを編集する
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
end
article一覧用のビューを作成する
article一覧を表示させます。
<h1>Articles</h1>
<ul>
<% @articles.each do |article| %>
<li>
<%= article.title %>
</li>
<li>
<%= article.body %>
</li>
<% end %>
</ul>
先consoleで作成した記事を表示されました。
ブラウザに記事を表示されるまでの流れ
1. ブラウザはリクエストを行います:GET http://localhost:3000/articles
2. Railsアプリケーションがこのリクエストを受信します。
3. Railsルーターは、ルートからArticlesControllerのindexアクションにマップします。
4. indexアクションは、Articleモデルを使用して、データベース内のすべての記事を取得します。
5. Railsは自動的にapp/views/articles/index.html.erb
ビューをレンダリングします。
6. ビューのERBコードをHTMLに変換されます。
7. サーバーはHTMLを含む応答をブラウザーに送信します。
article一覧ができました。
タイトルをクリックするとarticle詳細ページに飛ぶように作っていきます。
article詳細ページ用ルーティングを作成する
Rails.application.routes.draw do
get "/articles", to: "articles#index"
get "articles/:id", to: "articles#show"
end
コントローラーにshowアクションを追加する
class ArticlesController < ApplicationController
def show
@article = Article.find_by(id: [params[:id]])
if @article.nil?
flash[:danger] = "記事が見つかりませんでした。"
redirect_to root_path
end
end
end
app/views/articles/index.html.erb
にリンクを追加する
<h1>Articles</h1>
<ul>
<% @articles.each do |article| %>
<li>
<%= link_to article.title, article_path(article) %>
</li>
<li>
<%= article.body %>
</li>
<% end %>
</ul>
app/views/articles/show.html.erb
を作成する
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
これではCRUDのR(Read)が完了しました。
オペレーションごとにroutes
,controller
,views
を作成する必要があります。
resources
メソッドを使用してルーティングを定義すると、一般的なCRUDアクションに対するルートが自動的に生成されます。
articleのresourcesを作成する
routes.rb
を編集します。
Rails.application.routes.draw do
resources :articles
end
CRUDのルートが作成されました!
$ bin/rails routes
Prefix Verb URI Pattern Controller#Action
root GET / articles#index
articles GET /articles(.:format) articles#index
new_article GET /articles/new(.:format) articles#new
article GET /articles/:id(.:format) articles#show
POST /articles(.:format) articles#create
edit_article GET /articles/:id/edit(.:format) articles#edit
PATCH /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
app/views/articles/index.html.erb
のリンクを編集する
app/views/articles/index.html.erb
のリンクをもっと短く書けることが分かりました。
Prefix Verb URI Pattern Controller#Action
article GET /articles/:id(.:format) articles#show
<%= link_to article.title, article_path(article) %>
# 変える
<%= link_to article.title, article %>
パスを確認する時にbin/rails routes
が便利です。
次はCRUDのC(Create)、新規投稿の作成を作っていきます。
articleコントローラーにnewとcreateアクションを追加する
new
アクションではフォームをレンダリングします、create
アクションではバリデーションやDBへの保存を行います。
class ArticlesController < ApplicationController
def new
@article = Article.new
end
def create
@article = Article.new(article_params)
if @article.save
flash[:success] = "記事が作成できました。"
redirect_to @article
else
flash[:danger] = "記事の作成が失敗しました。もう一度試してください。"
render :new
end
end
private
def article_params
params.require(:article).permit(:title, :body)
end
end
Strong Parameters(ストロングパラメータ)は、Ruby on Rails フレームワークにおけるセキュアなパラメータの取り扱い方法です。一般的に、フォームからのユーザー入力データを受け取り、それをデータベースに保存する際に使用されます。
ストロングパラメータは、不正なデータの送信やセキュリティ上のリスクを防ぐため、特定の属性のみを許可する仕組みを提供します。これにより、ユーザーが意図せずにデータベースの他の属性を更新することや、マスアサインメント攻撃(Mass Assignment Attack)を防ぐことができます。
create
アクション内でparams.require(:article).permit(:title, :body)
を使用して、article
パラメータの中で許可する属性を指定しています。これにより、:title
と:body
のみが許可され、他の属性は無視されます。
redirect_to
とrender
の違い
redirect_to
はブラウザを別のURLにリダイレクトするためのメソッドです。新しいリクエストが発生します。
render
は同じアクション内でレスポンスを生成し、指定したビューファイルを描画します。同じリクエスト内で処理が行われます。
app/views/articles/new.html.erb
のリンクを作成する
投稿のタイトルと本文を入力できるようにフォームを作っていきます。
<h1>New Article</h1>
<%= form_with model: @article do |form| %>
<div>
<%= form.label :title %><br>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :body %><br>
<%= form.text_area :body %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
ブラウザとデベロッパーツールを確認します。
バリデーションに引っかかるかを確認します。
app/views/articles/index.html.erb
に新規投稿のリンクを追加する
article一覧から新規投稿を作成できるように/new
へのリンクを追加します。
<%= link_to "New Article", new_article_path %>
CRUDのC(Create)とR(Read)ができました。
CRUDのU(Update)、投稿の編集機能を作っていきます。
articleコントローラーにeditとupdateアクションを追加する
new
とcreate
アクションに似たようで、edit
アクションが編集のリクエストを送って編集画面edit.html.erb
を表示されます。update
アクションがフォームの検証とDBへの保存を行います。
class ArticlesController < ApplicationController
def edit
@article = Article.find_by(id: [params[:id]])
end
def update
@article = Article.find_by(id: [params[:id]])
if @article.update(article_params)
flash[:success] = "投稿が更新されました。"
redirect_to @article
else
flash[:danger] = "投稿の更新が失敗しました。もう一度試してください。"
render :edit
end
end
end
app/views/articles/edit.html.erb
を作成する
app/views/articles/new.html.erb
と同じフォームを使うため、app/views/articles/_form.html.erb
のパーシャルを作成します。
<%= form_with model: @article do |form| %>
<% article.errors.full_messages.each do |message| %>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<%= message %>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<% end %>
<div>
<%= form.label :title %><br>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :body %><br>
<%= form.text_area :body %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
app/views/articles/new.html.erb
とapp/views/articles/edit.html.erb
に_form.html.erb
を読み込みます。
<h1>New Article</h1>
<%= render "form", article: @article %>
<h1>Edit Article</h1>
<%= render "form", article: @article %>
上記のコードでは、
form
という名前の部分テンプレート_form.html.erb
をレンダリングし、その部分テンプレート内でarticle
というローカル変数に@article
の値を渡しています。
article
ローカル変数を使用して、@article
オブジェクトの属性にアクセスしたり、表示したりすることができます。コントローラーに定義された@article
をビューにも使われるようになります。通常、このような方法を使用して、フォームの構造や共通したコードを再利用することができます。
app/views/articles/show.html.erb
とapp/views/articles/index.html.erb
編集へのリンクを追加する
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
<li><%= link_to "Edit", edit_article_path(@article) %></li>
<h1>Articles</h1>
<ul>
<% @articles.each do |article| %>
<li>
<%= link_to article.title, article %>
</li>
<li>
<%= article.body %>
</li>
<li><%= link_to "Edit", edit_article_path(article) %></li>
<% end %>
</ul>
<%= link_to "New Article", new_article_path %>
最後はCRUDのD(Delete)、投稿の削除を追加していきます。
resources
メソッドがdestroy
アクションを用意していますのでそちらを使います。
articleコントローラーにdestroyアクションを追加する
class ArticlesController < ApplicationController
def destroy
@article = Article.find_by(id: [params[:id]])
@article.destroy
flash[:success] = "投稿が削除されました。"
redirect_to articles_path
end
end
app/views/articles/show.html.erb
に削除ボタンを追加する
<li><%= link_to "Delete", article_path(@article),
method: :delete, data: {confirm: "削除してよろしいでしょうか?"} %></li>
終わりに
公式ドキュメントを参考して簡単なCRUD機能を実装しました。
今度はユーザと紐つけるようにしていきたいと思います。
Discussion