はじめに
railsの環境構築で苦戦しどうにかこうにかrailsのトップページが表示できた後、達成感と同時に「で、どうやってWebアプリ作ればいいの?」という気持ちになりましたので、CRUD実装までをまとめました。
前提
本記事ではrails環境の構築は取り上げません。
database.ymlの設定なども済んでいる状態で進めます。
環境
$ sw_vers
ProductName: macOS
ProductVersion: 11.4
BuildVersion: 20F71
$ ruby -v
ruby 3.0.1p64 (2021-04-05 revision 0fb782ee38) [x86_64-darwin20]
$ rails -v
Rails 6.1.4
$ rbenv -v
rbenv 1.1.2
$ ruby-build --version
ruby-build 20210611
$ mysql --version
mysql Ver 8.0.25 for macos11.3 on x86_64 (Homebrew)
概要
超シンプルなTodoアプリを作ります。(というかただただDBに対してCRUDの処理を行うだけです。)
本当にCRUDのところだけですのでログイン機能なども作りません。
- 設計
- 準備
- model
- view
- controller
- CRUDの実装
- Read
- Create
- Update
- Delete
設計
今回やるTodoアプリで実装するCRUDの処理は以下の通り
Create:Todoを作成する処理
Read:Todoを表示する処理
Update:Todo内容を変更する処理
Delete:Todoを削除(完了)する処理
それぞれDBを操作するSQL文に置き換えるとこう
C:Todoの作成 => INSERT文
R:Todoの表示 => SELECT文
U:Todo内容の変更 => UPDATE文
D:Todoの削除 => DELETE文
完成図はこう
クライアントのリクエストに対してレスポンスを返すという基本的な流れは左側の部分。
DBに対して何かしらの操作(CRUD)が必要な場合はControllerがModelを呼び出してグレーの部分の処理を行います。
実装
CRUDの前にまずは、クライアントからのリクエストに対して静的なページを返すところまで作成します。
図にするとこう。グレーのところは通りません。
Viewの作成
Viewの作成ではindex.html.erbの作成/todo_controller.rbの作成/routes.rbの編集を行います。
要はユーザのアクセスに対して適切なhtmlを表示するためのに必要なものをここで作ります。
index.html.erbの作成
まずはこのコマンドを実行します。 ※以降出てくるコマンドは全てrails newで作成または実行したディレクトリの直下で実行しています。
$ rails g controller todo
これを実行することでhtmlを作成するためのフォルダがapp/view/配下に作成されます。(Controllerも作成されますが、編集するのは次です。)
app/view/todo 配下に表示するhtmlファイルを作成します。
$ touch app/views/todo/index.html.erb
railsでhtmlを書くときはerbという拡張機能を用いて書きます。(haml、slimもありますが今回はerbを使います。)
通常のhtmlと何が違うかというとざっくり以下の通り。
-
拡張子が違う
html.erbという拡張子になっています。 -
htmlの中にrubyの記述ができる。
htmlはそのまま使うと決まった内容しか返せませんが、html.erbだと特殊なタグで囲むことによってrubyを記述することができます。
つまり、controllerで処理した結果を変数で表示したりDBの内容をループして表示したり、if文を用いた条件分岐で表示させる内容を切り替えたりできます。
まず最初はviewを表示することを考えましょう。その後にerbの記述をしていきます。
<h1>TOPページ</h1>
これだけです。
通常のhtmlで記載するhtmlやbodyタグは?と思われるかと思いますが、これだけでOKです。
(application.html.erbとyeldというタグについて調べると理解できるかと思います。)
以上でindex.html.erbの作成は完了です。
controller_todo.rbの作成と編集
index.html.erbを作成しただけではrailsはそのファイルをユーザにレスポンスとして返してはくれません。
Controllerにindex.html.erbを返す処理を記載する必要があります。
Controllerは先程の「rails g controllerコマンドで作成されていて app/controllers/配下にtodo_controller.rb
があります。
ここに先程作成したindex.html.erbを表示する処理を記述します。
記載の内容は以下の通り
class TodoController < ApplicationController
def index #追記
end #追記
end
indexというメソッドを記述するだけです。内容はなくてもOKです。
こうすることでviewフォルダ配下のindex.html.erbを返してくれます。
routes.rbの編集
これでindex.html.erbを表示する処理がかけましたが、まだユーザはindex.html.erbにたどり着くことはできません。
なぜなら、todo_controller.rbのindexメソッドにアクセスするためのルートがないからです。
ルートはconfig配下のroutes.rbに記載します。
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
get '/todo', to: "todo#index", as: "top" #追記
end
こうすることで、http://[railsのip]:[port]/にgetリクエストをした時にtodo_controller.rbのindexメソッドにルーティングされます。routes.rbの書き方は他にもあるので調べてみてください。
以下のコマンドで現在のルートの確認ができます。
$ rails routes
(結果一部抜粋)
Prefix Verb URI Pattern Controller#Action
top GET /todo todo#index
ここに先程追加した内容が表示されればOKです。
ここまでで
作成したindex.html.erbが表示されればほとんどできたも同然です。
作成したページにアクセスしてみます。
まずはサーバのスタート
$ rails s
ローカルホスト3000ポートにアクセスして先程作成したindex.html.erbが表示されればOKです。
http://localhost:3000/todo
Read処理の実装
Read処理でやることは、DBからデータを取得して取得したデータをindex.html.erb内に含めてユーザに返すことです。
実際にはDBの中のtableに対してSELECT文を実行して値を取得しています。
図にするとこう.
DBからデータを取得する処理はtodo_controller.rbの中でModelのインスタンスを作成しデータを取得します。
Modelの作成
Modelの作成ではtodo.rb/migrateファイル/database/tableを作成します。
Modelの作成は以下のコマンドで行います。
$ rails g model Todo title:string comment:string limit:date
このコマンドを実行するとapp/models/配下にtodo.rbとdb/migrate配下にXXXX_create_todos.rbファイルが作成されます。
todo.rbはActiveRecordというクラスを継承しておりDBとのやりとりを円滑に行うためのメソッドを使えます。
XXXX_create_todos.rbはテーブルの情報をrubyの記述で書いたものです。これをコマンドでマイグレートするとXXXX_create_todos.rbで定義したテーブルが作成できます。
DBの準備
以下のコマンドでdatabase.ymlに記載されている情報をもとにDBを作成します。
$ rails db:create
さらに以下のコマンドを実行することで先程作成したXXXX_create_todos.rbをマイグレートし、DBないにテーブルを作成します。
$ rails db:migrate
完了したらRead処理のために何か適当なデータを投入しましょう(INSERT、irb、SEEDなど、投入もとはなんでも)
今回準備したデータは以下の通り
mysql> select * from todos;
+----+-------+------------+------------+----------------------------+----------------------------+
| id | title | comment | limit | created_at | updated_at |
+----+-------+------------+------------+----------------------------+----------------------------+
| 1 | test1 | test-todo1 | 2021-08-10 | 2021-08-07 14:10:00.736786 | 2021-08-07 14:10:00.736786 |
| 2 | test2 | test-todo2 | 2021-08-10 | 2021-08-07 14:10:15.833124 | 2021-08-07 14:10:15.833124 |
| 3 | test3 | test-todo3 | 2021-08-10 | 2021-08-07 14:10:22.733875 | 2021-08-07 14:10:22.733875 |
| 4 | test4 | test-todo4 | 2021-08-10 | 2021-08-07 14:10:30.992338 | 2021-08-07 14:10:30.992338 |
| 5 | test5 | test-todo5 | 2021-08-10 | 2021-08-07 14:10:37.590373 | 2021-08-07 14:10:37.590373 |
+----+-------+------------+------------+----------------------------+----------------------------+
5 rows in set (0.00 sec)
DBからデータを取得する
todo_controller.rbのindexメソッド内に以下を記述します。
class TodoController < ApplicationController
def index
@todos = Todo.all() #追記
end
end
先程作成したtodo.rbで定義されているTodoというクラスのallというメソッドを用いてtodosテーブルのデータを全て取得して@todosという変数に格納しています。
@のついた変数はインスタンス変数を示す記号でインスタンスが存在する間有効な変数です。
これを指定することでこの変数をindex.html.erb内で扱うことができます。
1つのテーブルにつき1つのModelが対応していて、railsにおいてテーブルとやりとりをする場合このModelを通して行います。
Controllerの記述は以上。
index.html.erbに取得したデータを表示させる。
@todosはtodo_controller.rbでTodoクラスのallメソッドを用いて取得したデータです。
todosテーブルから取得した全てのレコードが配列として格納されています。配列の要素一つ一つはTodoクラスのインスタントなっておりtodosテーブルの1行分のデータを持っています。
この格納されたインスタンスたち(テーブルの1行ずつ)をeach文で回しそれぞれ必要なカラムのデータを表示します。
コードはこう
<h1> TOPページ </h1>
<% @todos.each do | todo |%>
<ul>
<li><%= todo.title %></li>
<li><%= todo.comment %></li>
<li><%= todo.limit %></li>
</ul>
<h1>----------</h1>
<% end %>
erbの詳細は省きますが、<% %>で囲まれているところはrubyの処理が書いてあります。
今回はeach文で繰り返し処理をしているので<% end %>までの記述が繰り返されています。
<%= %>で囲まれているところもrubyの処理が書いてあります。先程との違いはhtmlに表示するかしないかです。
DBから取得したTodoの内容はブラウザに表示させたいので<%= %>で囲んでいます。
eachやifの様な制御構文はブラウザに表示させる必要がないので<% %>で囲みます。
ちゃんとDBの内容が表示されているのがわかります。
ブラウザからhtmlのソースを見てみると
ちゃんと<% @todos.each do | todo | %> から<%= end %>までで囲んだ箇所が繰り返されているのがわかります。
(index.html.erbには1つ分しか書いていないのに、ブラウザのソースでは取得した分ちゃんと書かれている。)
ここまでやると
ここまでやった時の図はこうです
文章にするとこう
- clientがhttp://localhost:3000の"/todo"にGetリクエストを送った時、routes.rbによって
todo_controller.rbのindexメソッドが実行されます。 - indexメソッドは、Todoインスタンスのallメソッドを実行しtodoテーブルから全てのデータを取得しindex.html.erbに渡すためのインスタンス変数@todosに格納します。
- index.html.erbは受け取った@todosの中のデータをeachで順番に表示します。
Read処理は以上
Create処理の実装
Create処理でやることはindex.html.erbで入力フォームの作成/todo_controller.rbでcreateメソッドの作成/route.rbの編集です。
実際にはDBに対してINSERT INTO分が実行されています。
Create用ルートの編集
route.rbを編集します。
先程index.html.erbのform_withで指定したurlを作成してあげます。
今回はデータの追加を行うpostメソッドを指定します(httpメソッドについて詳細は省きます)
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
get '/todo', to: "todo#index", as: "top"
post '/todo', to: "todo#create", as:"create" #追記
end
Controllerでの処理を作成
todo_controller.rbではindexメソッドへの追記とcreateメソッド、todo_paramsメソッドの作成を行います。
class TodoController < ApplicationController
def index
@todos = Todo.all()
class TodoController < ApplicationController
def index
@todos = Todo.all()
# Viewのformで使う空のTodoインスタンスを作成します。
# Viewはこのインスタンスにユーザから入力された値を入れてControllerに渡します。
@new_todo = Todo.new #追記
end
### 以下追記 ###
def create
# Viewからの入力を受け取りインスタンスを作成します。
@todo = Todo.new(todo_params)
# DBに値を追加した時の結果よるアクションを返します。
# 今回は成功しても失敗しても画面を更新するだけです。
respond_to do |format|
if @todo.save
format.html {redirect_to request.referer}
else
format.html {redirect_to request.referer}
end
end
end
private
# StorongParameterと言ってセキュリティに関係するメソッド
# todoモデル(:todo)で入力を許可するカラムを記載する。これ以外のデータは受け付けない様になる。
def todo_params
params.require(:todo).permit(:title, :comment, :limit)
end
end
以上でtodo_controller.rbの編集は完了
Create用にViewの編集
入力フォームを作成するときはform_withを使います。
index.html.erbの先頭に次のコードを追記
<h1> TOPページ </h1>
<%# todo_controller.rbで定義した空のインスタンスに値を格納しcreate_pathにPOSTします。 %>
<%= form_with model: @new_todo, url:create_path do |form| %>
<%= form.label :title %>
<%= form.text_field :title%>
<%= form.label :comment %>
<%= form.text_field :comment %>
<%= form.label :limit %>
<%= form.date_field :limit %>
<%= form.submit %>
<% end %>
(省略)
Createの確認
ここまでやったらブラウザの表示はこう
ちゃんと入力フォームができています。この様にデータを入力してCreateTodoをクリックすると画面最下部に作成したTodoが追加されています。
ちなみにReadで取得したDBの値は取得した順番で並んでいます。新しいものを先頭にしたい場合は並べ替える必要があります。
id9のデータ として追加されています。(テストしている時にid6,7,8と余計なデータを作成していますが気にしないでください)
ここまで
文章にするとこうです。
- index.html.erbの入力フォームにデータを入力してCreateボタンをクリックするとhttp://localhost:3000/todo/createにPostリクエストが送信され、routes.rbによってtodo_controller.rbのcreateメソッドが実行されます。
- createメソッドはindex.html.erbから受け取ったデータをDBに追加し画面の更新をします。
- 画面更新により再びhttp://localhost:3000/todoにGETリクエストが送信されます。その時もtodo_controller.rbのindexメソッドが実行されtodoテーブルのデータが改めて取得されるため先程追加したデータが画面に表示されてきます。
Createの実装は以上
Updateの実装
Update処理でやることは index.html.erbで更新のリンク作成/todo_controller.rbでupdateメソッドの作成/ route.rbでルートの編集です。
実施にはDBに対してUPDAET文が実行されます。
Update用ルートの編集
route.rbに追記します。
今回はデータの更新を行うpatchメソッドを指定しします。
宛先はこの後作成するtodo_controller.rbのupdateメソッドを指定します。
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
get '/todo', to: "todo#index", as: "top"
post '/todo/create', to: "todo#create", as:"create"
patch '/todo/update', to: "todo#update", as:"update" #追記
end
Controllerでupdateメソッドを作成
todo_controller.rbに以下のメソッドを追記します。
def update
## viewから変更対象のデータのIDを受け取る
todo_id = params[:id]
## todosテーブルから受け取ったIDと一致するインスタンス(行)を取得
@update_todo = Todo.find(todo_id)
## 取得したインスタンスのcomment(取得した行のcommentカラム)に"完了"を格納
@update_todo.comment = "完了"
## 実行した時の処理を記載(createと同じ)
respond_to do |format|
if @update_todo.save
format.html {redirect_to request.referer}
else
format.html {redirect_to request.referer}
end
end
end
Update用にViewを編集
<h1> TOPページ </h1>
<%= form_with model: @new_todo, url:create_path do |form| %>
<%= form.label :title %>
<%= form.text_field :title%>
<%= form.label :comment %>
<%= form.text_field :comment %>
<%= form.label :limit %>
<%= form.date_field :limit %>
<%= form.submit %>
<% end %>
<% @todos.each do | todo |%>
<ul>
<li>id<%= todo.id%>のデータ</li>
<li><%= todo.title %></li>
<li><%= todo.comment %></li>
<li><%= todo.limit %></li>
<%# 以下の一行を追加してリンクを表示 %>
<%= link_to "更新", update_path(id: todo.id), method: :patch %>
</ul>
<h1>----------</h1>
<% end %>
追加した行により、さきほどroute.rbに追加したupdate_pathにpatchリクエストを送信します。
その際にidというパラメータを一緒に送信します。これにはtodo.idの値(表示しているデータのid)が入っていて、どのデータを更新するのか判別するために送信しています.
todo_controller.rbのupdateメソッド側ではtodo_id = params[:id]のところで受け取っています。
Updateの確認
ここまでやるとブラウザの表示はこう
更新というリンクが各データの下に着きましたね。
コメントを表示している箇所が"完了"に更新されるかと思います。
ここまで
文章にするとこうです
- index.html.erbの"更新"リンクをクリックするとhttp://localhost:3000/todo/updateにPATCHリクエストが送信され、routes.rbによってtodo_controller.rbのupdateメソッドが実行されます。
- updateメソッドはindex.html.erbから受け取ったidをもとに更新対象のでーたをtodoテーブルから探します。
- updateメソッドは対象のデータの中のcommentカラムのデータを"完了"に書き換え保存し、画面更新を行います。
- 画面更新により再びhttp://localhost:3000/todoにGETリクエストが送信されます。その時もtodo_controller.rbのindexメソッドが実行されtodoテーブルのデータが改めて取得されるため先程更新したデータが画面に表示されてきます。
Updateの実装は以上
Deleteの実装
CRUDの最後になります。
Delete処理でやることは、index.html.erbで削除用のリンク作成/todo_controller.rbでdeleteメソッドの作成/ route.rbでルートの編集です。
お気づきかもしれませんがUpdate処理と同じです。
todo_controller.rbで行う処理が少し違うだけですのでサラッと
Delete用ルートの編集
Rails.application.routes.draw do
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
get '/todo', to: "todo#index", as: "top"
post '/todo/create', to: "todo#create", as:"create"
patch '/todo/update', to: "todo#update", as:"update"
delete '/todo/delete', to: "todo#delete", as:"delete" #追記
end
特に補足することはありませんね。
Controllerでupdateメソッドを作成
def delete
## viewから変更対象のデータのIDを受け取る
todo_id = params[:id]
## todosテーブルから受け取ったIDと一致するインスタンス(行)を取得
@delete_todo = Todo.find(todo_id)
## 削除の実行実行と実行した時の処理を記載
respond_to do |format|
if @delete_todo.destroy
format.html {redirect_to request.referer}
else
format.html {redirect_to request.referer}
end
end
end
updateでは@update_todo.comment = "完了"として値を変更していましたが、
今回はdestroyメソッドを利用して対象のデータを削除します。
Update用にViewを編集
<h1> TOPページ </h1>
<%# todo_controller.rbで定義した空のインスタンスに値を格納しcreate_pathにPOSTします。 %>
<%= form_with model: @new_todo, url:create_path do |form| %>
<%= form.label :title %>
<%= form.text_field :title%>
<%= form.label :comment %>
<%= form.text_field :comment %>
<%= form.label :limit %>
<%= form.date_field :limit %>
<%= form.submit %>
<% end %>
<% @todos.each do | todo |%>
<ul>
<li>id<%= todo.id%>のデータ</li>
<li><%= todo.title %></li>
<li><%= todo.comment %></li>
<li><%= todo.limit %></li>
<%= link_to "更新", update_path(id: todo.id), method: :patch %>
<%# ここに削除のリンクを追加 %>
<%= link_to "削除", delete_path(id: todo.id), method: :delete %>
</ul>
<h1>----------</h1>
<% end %>
更新用のリンクと同じくリンクを作成します。
先程route.rbにdeleteメソッドを指定しているためdeleteメソッドを指定します。
また、削除対象のデータを探すためにidを渡します。
Deleteの確認
ここまでやるとブラウザの表示はこう
削除のリンクが表示されていますね
ここまで
文章にするとこう
- index.html.erbの"削除"リンクをクリックするとhttp://localhost:3000/todo/deleteにDELETEリクエストが送信され、routes.rbによってtodo_controller.rbのdeleteメソッドが実行されます。
- deleteメソッドはindex.html.erbから受け取ったidをもとに更新対象のでーたをtodoテーブルから探します。
- deleteメソッドは対象のデータを削除します。
- 画面更新により再びhttp://localhost:3000/todoにGETリクエストが送信されます。その時もtodo_controller.rbのindexメソッドが実行されtodoテーブルのデータが改めて取得されるため先程更新したデータは画面からきえています。
Deleteの実装は以上です
これで超シンプルなCRUD処理の実装は完了です。
最後に
長くなりましたが、以上でCRUD処理の実装は完了です。
冒頭にも申し上げた通り、超シンプルなCRUDの処理だけを実装したのでWebアプリとして使える様なものではありません。しかしWebアプリの骨になる部分はこれで作れる様になるかと思います。
あとは、ログイン機能をつけてユーザごとに異なる情報を表示したりとか、ユーザがデータの入出力をしやすい様にとか、外部のサービスと連携して便利な機能を使うとかを肉付けしていく様に勉強していくのが良いのかなーと個人的に思っています。
ここまで読んでくださった方がいらっしゃいましたら嬉しい限りです。ありがとうございます。