初心者用Railsアプリ練習帳
勉強会を開き、Rails初学者に日頃教えています。初心者向け勉強回資料用もかねて作成。
ある程度基礎を学習していると、読みながら真似はできるけど、いまいち全体の知識がつながっていないので自分で作ることができない…
そんな状態になりがちでした。
対象者
- Railsでなんでもいいからの教材をやった人
- Rails基礎はなんとなくわかってきた人
- Railsで自分でアプリ作るのはまだ無理そうという人
- Rails以外のフレームワークならわかる人
※ 正確さより概念とアプリ作成の流れを重視します。
コンセプト
他にも言っている方がいますが、勉強会で教えている経験から初学者が同じ教材を何周もするのは上達が遅いように感じています。
知識はあるけど自分で一から作るのは無理そう。
エラーが出て対処出来ない
ということになりがちでした。
こういう場合、実際に手を動かし、自分の頭で考えて簡単なアプリを作る必要があるけど、
- いきなりだと何を作ればいいのかわからない…
- 一連のアプリを作っていく順番がわからない…
- エラーでても対処できない…
- 簡単なのから作ってみればいいと言うが何が簡単なものなのか分からない…
- 何個もアプリ自分で作るといいらしいが何を作れば練習やスキル向上なるか分からない…
そんな人が未経験からプログラミングをやる人には何人もいました。
現状の私の考えですが、世の中にある教材は見た目とか完成度ないと売れないし、強い人達からツッコまれるので分かりやすさより精確さが必要とされる教材になってしまうようにおもいます。
なので、後で身につければ良いこともあれもこれも付け足しており、本当にコレだけ!
という事項が初心者には見えにくくなっている気がします。
それをうけて今回、ムダなものとにかく剥ぎまくって、コアな部分だけを数多く練習すればみんな出来るようになるのではないかと考え、練習問題を作りました。
今後も感想とかもらって継続的にブラッシュアップしていければと思っています。
手順
- とりあえずToDoアプリをなぞって作ってみる(少しだけでも勉強した人が前提) ←今回はココ
- 超シンプルアプリを量産。手を動かして覚える!概念つかむ!
- データベースから欲しいデータを取得する!千本ノックのようにやりまくります!
2,3は個人的に開く勉強会で解説します。
とりあえずToDoアプリをなぞって作ってみる
環境構築はローカルでもいいですけど、WindowsやMacで違うので初心者はcloud9で作っていいと思います。
構築方法は今回省略
以下のコマンドでアプリを作成。プロジェクト名になります。なんでもいいです。
今回、私はrails-todo1
にしました
$ rails new rails-todo1
沢山のファイルが作られる。終わったらプロジェクトのディレクトリに移動
$ cd rails-todo1
サーバーを起動させて表示させてみる
$ rails s
こんな画面が出たらOK
これから中身を作っていくので一応流れを確認
今回新規作成で一から作っていきますが、だいたいこんな感じで作ると考えていただければよいかと思います。
新しいアプリ(ページ)を作る手順
全体の流れで考えること
- 作るページはどんなページで作る?
- データベースに保存するデータは何か?
- モデルは作ったか?
- マイグレーションファイルを生成&データベースに反映
generate modelコマンドするとmigrationファイルも作られる。
カラム(データベースの列)を追加したり、データベースの中身を変えたいときにもマイグレーションファイル使う
-
rails db:migrate
でマイグレーションファイルの内容をデータベースに反映
- ページを表示するのか?データを登録・更新をするのか?
表示するならGET
データの登録ならPOST
データの更新ならPATCH (PUT)
データの削除ならDELETE
- ルーティングを決める
URLにパラメータをいれる必要があるのか?(例えば各ユーザーの情報ページを表示するならparams[:id]のように書く。ならばURLに〇〇〇/:idのようにidを含める必要がある)
URLにパラメータいらない(新規登録や全データ表示など)
- 処理を行うコントローラーは作ったか
- コントローラーのアクションは作ったか
-
GET?POST?PATCH?DELETE?
かを確認してつくる
- ビューファイルはいるか?
さっそく簡単なアプリ作成していく
1.どんなもの作るか
2.データベースに保存するデータは何か
- テーブル名:Todo
- カラム名:title
- デフォルトで作られるカラム(ID, 作成日, 更新日)
3. モデルは作ったか
今回は、まだ何もつくってないので当然必要。
今回作るToDoアプリでは、Todoテーブルのデータ取得・新規登録・データ更新・データ削除
を行うのでModelを1つ作成します
というわけで、モデル作成。
$ rails generate model Todo title:string
2.で書いたように必要なテーブルがTodo
必要なカラムがtitle
なので、このように書いています。
① rails generate model Todo
でモデルファイルが作られる
② title:string
部分は追加したいカラム名と型。
この部分は書かなくても手動で記述すれば作れるのですが、コマンドでこのように打ったほうが楽です
class CreateTodos < ActiveRecord::Migration[5.2]
def change
create_table :todos do |t|
t.string :title
t.timestamps
end
end
end
補足:モデルとは
普通はデータベースからデータを取得するときにはSQL
というものを書かないといけなくて初心者にはそれなりに複雑。
Railsではmodelというものを使うとSQL書かなくてもデータ取得できて便利。
4. マイグレーションファイルを生成&データベースに反映
今回新規作成なのでモデルをつくるとmigrationファイルも作られるのでコマンドでmigrationファイルを作る必要ありません。
マイグレーションファイルの内容をデータベースに反映させるコマンド打つ
$ rails db:migrate
補足:migrationファイル作る必要あるときは
migrationファイルとは:
データベースのテーブルの作成、変更を記述するためのファイルで、rails db:migrate
を実行することでデータベースに反映させる。
【コマンド詳細】カラムの追加。今回は実行必要しないでください
$ rails generate migration add_password_digest_to_users password_digest:string
こんな感じのコマンド入れてカラムの追加とかやります。
今回は当然新規必要なし。
実行するとmigrate
でエラーになります。
5. ページを表示するのか?データを登録・更新をするのか?
→ 表示するならGET
→ データの登録ならPOST
→ データの更新ならPATCH (PUT)
→ データの削除ならDELETE
Todoを追加するための機能を持つページを「表示」します。
ということでGET
メソッドです
6. ルーティングを決める
ルーティングは
どのURLにアクセスすると、どのコントローラーとアクションを使うのか定義するものです。
Railsにはresources
という便利機能があってルーティング書くのを便利にできる機能があるんですが、今回は原理を理解するために今回は一つ一つルーティングを記述していきましょう。
get '/todos', to: 'todos#index', as: 'todos'
post '/todos', to: 'todos#create'
ルーティングを確認する
コンソールに↓を入力するとルーティングを確認できる。
$ rails routes
Prefix Verb URI Pattern Controller#Action
todos GET /todos(.:format) todos#index
POST /todos(.:format) todos#create
もしくは
/rails/info/routes
をアドレスバーにつけると少し見やすい画面になる
http://sample.com/rails/info/routes
みたいな感じ
他タブで開いておくといちいちコマンド入れなくても確認できるので便利!
現状では/todos
にGETを設定してtodoコントローラー
のindexアクション
の処理を使えと指定している
補足:_pathについて
$ rails routes
だとPrefix
がtodos
という名前になっていてURLに名前を付けて便利に使えるようになっている。これに_path
を追加すると使えます。
todos
+ _path
でtodos_path
です。
todos_path
と書くと/todos
と書いたのと同じことになります。
結構使いますので覚えておくといいです。
7. 処理を行うコントローラーは作ったか
コントローラーは大雑把にルーティング
とモデル
とビュー
をつなぐ役割、データ返す役割をしているものです。
コントローラーは何をするか雑に説明
細かく言うとわかりにくくなるので大雑把に。
- ルーティングで該当のURLにマッチしたら、コントローラーのアクションに処理が飛んでくる。そして処理を行う。
- その際に
params
のデータもらうこともあり
- その際に
-
Todo.new
使ってビューファイルで記述するform_with
(form_forも含む)で使うフォームでデータを送信するために必要なモデルの部品みたいなものが入った箱作ったりする。- ビューで使うならインスタンス変数に格納する
-
Todo.find(params[:id])
みたいに自分で作ったModel
使ってデータベースの値を取得する。-
Todo.find(params[:id])
はTodo.new
で作った部品に、取得したデータも含んだものだと思っておけばいいです。※下図参照 - ビューで使うならインスタンス変数に格納する
-
- インスタンス変数はビューで使用可能なのでページを操作によってデータ表示やフォームの送り先を変えられる動的なページを作れる
初心者のうちはデータ取得したり、データを加工する、ビューで表示するためのデータを@todo
みたいなインスタンス変数を作る!
みたいな感じでいいと思います。
コントローラー作成コマンド
以下コマンドで、Controllerを作成します。
とりあえずコントローラーを生成する
$ rails g controller Todos
このコマンドならtodosというコントローラー作成されるので、todos_controller.rb
というファイルができます。
8. コントローラーのアクションは作ったか
class TodosController < ApplicationController
def index
@todos = Todo.all
end
end
コントローラーの処理を書いてみました。
Todoテーブルに入っているデータを全件取得(allメソッドを使う)してインスタンス変数の@todos
に入れています。
9. ビューファイルはいるか?
ビューファイルは表示部分です。
htmlっぽい書き方だけどコントローラーなどで処理したインスタンス変数などを使って静的なページでなく動的なページを作ることができます。
さきほど、@todos
というインスタンス変数をコントローラーのindexアクションに書いたので、これを作って全データを表示してみましょう。
index.html.erbが存在しない場合はapp/views/todos/index.html.erb
に作成しましょう。
<h1>ToDo一覧</h1>
<table>
<thead>
<tr>
<th>タスク</th>
</tr>
</thead>
<tbody>
<% @todos.each do |todo| %>
<tr>
<td><%= todo.title %></td>
</tr>
<% end %>
</tbody>
</table>
作成した画面
まだデータの登録がないので全件取得しても表示するものがないので、このような表示になっています。
こんな1~9までのような順序でアプリの作成(もしくは追加)をしていくことになります。
では、次にTodoの全件表示しかできない状態なので
次は新規ToDo追加できる機能を追加します
新規追加機能の作り方
次に新規登録できる機能を作ります。
今度は1~9まの順序で機能を追加していきましょう。
1.どんなもの作るか
ToDoテーブルにデータを新規追加する機能
テキストフィールドに入力した文字をボタン押すとデータベースに登録
2.データベースに保存するデータは何か
テーブル名:Todo
カラム名:title
デフォルトで作られるカラム(ID, 作成日, 更新日)
title
だけデータベースに保存すればいいことになります。
3. モデルは作ったか
モデルはTodoつかうので新しく作成の必要なし
4. マイグレーションファイルを生成&データベースに反映
モデルはTodo使う。カラムの追加も必要ないのでマイグレーションファイル作る必要も、データベースに反映する必要もない。
5. ページを表示するのか?データを登録・更新をするのか?
カラム名でtitle
をデータベースに登録する処理を追加することになります。
ページの表示をしているわけでも、情報の更新をしているわけでもなく、情報の削除でもありません。
こういうデータの登録処理はPOST
で行いましょう。
6. ルーティングを決める
- メソッド:
POST
- ルーティング:
/tasks
- コントローラー&アクション
tasks#create
ルーティングを確認
実はすでに追加してしまっていたのですが、今回使うルーティングは↓の二つ目です。
Rails.application.routes.draw do
get '/todos', to: 'todos#index', as: 'todos'
post '/todos', to: 'todos#create'
end
ページを表示しないで新規登録を行うのでPOSTですね。
7. 処理を行うコントローラーは作ったか
新しいモデルを作ったわけでも、わざわざ新しいコントローラーをつくる必要もありません。
todo
テーブルのToDoのデータ追加処理を追加するのでコントローラーはtodos_controller.rb
に追記すればOKです。
8. コントローラーのアクションは作ったか
データ追加処理を記述したアクションをtodos_controller.rb
に追記していきましょう。
コントローラーにアクションを追加
create
を追加しました。新規登録を行うためのアクションになります。
class TodosController < ApplicationController
def index
@todos = Todo.all
@todo = Todo.new # フォーム作るために必要
end
def create
@todo = Todo.create(todo_params)
redirect_to todos_path
end
end
redirect_to todos_path
としているのは、ページを表示しているわけでないのでリダイレクトしないと処理が終わっても処理が固まった感じになってしまうからです。
処理としては正常に終了してるけど、↓のように何も起こってない…みたいな感じになる。
正常に登録はできているのでリロードするとちゃんと表示されることになる。
get '/todos', to: 'todos#index', as: 'todos'
のas: 'todos'
と書くと/todos
というURLにtodos
という名前を付けて使えるようにしています。
この名前に、ルーティングの名前(todosなど)
+ _path
で todos_path
のようにするといちいちURLを書く必要がなくなります。
注意点として、Formで入力した情報を新規登録や更新する際はセキュリティ対策としてStrong Parameters
を使わないとエラーでますので追加します。
Strong Parameters
セキュリティ対策で、入れないとエラーになります。
難しいこと省いて、フォームから受け取ったデータを渡すと、許可したデータだけ返却されるくらい覚えておけばとりあえずは良いと思います。
そして追加したのがtodo_params
メソッド。また、private
を使うと、todo_paramsメソッドが外部から使えないようにできるのでセキュリティを高まります。
難しいことはいいので、privateの下にStrong Parameters
いれるんだなくらいで今はいいです。
class TodosController < ApplicationController
def index
@todos = Todo.all
@todo = Todo.new # フォーム作るために必要
end
def create
@todo = Todo.create(todo_params)
redirect_to todos_path
end
private
def todo_params
params.require(:todo).permit(:title)
end
end
9. ビューファイルはいるか?
データベースでのデータの追加処理では使いません。
ただし、送信するためのテキスト入力フォーム
と送信ボタン
をindex.html.erb
に登録したいと思います。
テキスト入力フォームと送信ボタン
<h1>ToDo一覧</h1>
<table>
<thead>
<tr>
<th>タスク</th>
</tr>
</thead>
<tbody>
<% @todos.each do |todo| %>
<tr>
<td><%= todo.title %></td>
</tr>
<% end %>
</tbody>
</table>
<h2>ToDo追加</h2>
<%= form_with model: @todo do |f| %>
<div><%= f.label :title %></div>
<div><%= f.text_field :title %></div>
<div><%= f.submit %></div>
<% end %>
フォームを作るためにform_with
を使っています。
また、使うmodelを指定する必要があるので@todo
を指定してます。
コントローラーのindexアクションで追加します。
f.label
のようにすると「Title」というラベル(文字表示)され、
f.text_field
は文字を入力するフォームを作ることができる。
f.submit
は送信ボタン。
todos_controller
のindex
にフォームを作るための部品を用意するため@todo = Todo.new
を追加
class TodosController < ApplicationController
def index
@todos = Todo.all
@todo = Todo.new # フォーム作るために必要
end
end
登録機能完成
データベースに登録する機能が完成しました!
今度は更新と削除
次は更新機能を実装します。
必要機能
- 情報更新ページ(今回はeditアクションとedit.html.erbにて)
- ルーティングを追加する
- editアクションで編集する情報を表示する todos_controller.rb
- ビューで見た目を作る edit.html.erb
- 情報更新処理
- updateアクションで情報の更新処理 todos_controller.rb
更新情報編集ページ用ルーティング追加
Rails.application.routes.draw do
get '/todos', to: 'todos#index', as: 'todos'
post '/todos', to: 'todos#create'
get '/todos/:id/edit', to: 'todos#edit', as: 'edit_todo'
end
get '/todos/:id/edit', to: 'todos#edit', as: 'edit_todo'
を追加しました。
todos_controller.rb
のedit
アクションを使用します。ルーティングの名前はedit_todo
とでもしておきます。
更新情報編集ページ用コントローラー
editアクションは、情報編集画面(/todos/:id/edit)にアクセスすると処理されるアクションで、ビューで見た目を作るときに必要なデータの取得します。
editアクションを情報を追加します。
def edit
@todo = Todo.find(params[:id])
end
-
@todo
はインスタンス変数。@
とつけることでビューでもデータを使えるようになります。 -
find
はデータベースのid
カラムからデータを探し出すときに使います。- idはtodoを追加したときに自動的に採番されていきます。データベースの中身は下の表みたいになる。
- 編集画面は(/todos/:id/edit)のようにルーティングで設定した。
- 例えばURLが
http://sample.com/todos/1/edit
だとするとparams[:id]
は1
-
http://sample.com/todos/3/edit
だとするとparams[:id]
は3
-
http://sample.com/todos/1/edit
だとすると@todo = Todo.find(1)
と書いているのと同じになる
- 例えばURLが
試しに登録したデータベースの内容が↓になります。
id | title | created_at | updated_at |
---|---|---|---|
1 | テスト | 2019-10-18 04:05:36.776003 | 2019-10-18 04:05:36.776003 |
2 | 買い物 | 2019-10-18 04:05:43.090180 | 2019-10-18 04:05:43.090180 |
3 | 勉強 | 2019-10-18 04:05:51.098753 | 2019-10-18 04:05:51.098753 |
@todo = Todo.find(params[:id])
でid
が1
だったら取得するデータをbyebugというデバッグツールで確認してみると
#<Todo id: 1, title: "テスト", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 04:05:36">
というデータが取れます。
ここで注意点
教えていて勘違いしている方が多かったので補足。
今回title
カラムのデータを取得したいわけです。
@todo = Todo.find(params[:id])
をするとtitle
のデータだけ
を取得しているのではありません。意図的にデータを絞り込まなければ、今回の場合
id
, title
, created_at
, updated_at
1レコード(1行)分のデータ取得されます。
@todo = Todo.find(params[:id])
idは1とすると
正:
#<Todo id: 1, title: "テスト", created_at: "2019-10-18 04:05:36", updated_at: "2019-10-18 04:05:36">
誤:
#<Todo title: "テスト">
自分が必要だと思っているデータだけ取得できてると勘違いしている??ように思えましたので補足。
こらへんはSQLを理解していないから起こることなのかとも思います。
気を付けましょう。
更新処理アクション追加
ルーティングとコントローラーに追記していきます。
resources使うともう少しシンプルに書けますが、今回は原理を知るためしっかりと書いていきます。
Rails.application.routes.draw do
get '/todos', to: 'todos#index', as: 'todos'
post '/todos', to: 'todos#create'
get '/todos/:id/edit', to: 'todos#edit', as: 'edit_todo'
patch '/todos/:id', to: 'todos#update' # 更新処理用ルーティング追加
end
def update
@todo = Todo.find(params[:id])
@todo.update(todo_params)
redirect_to todos_path
end
```
resouses使えるならこんな感じでシンプルに書ける
```.erb
<-- resousesを使えるときなら -->
<h1>ToDo編集</h1>
<%= form_with model: @todo do |f| %>
<div><%= f.label :title %></div>
<div><%= f.text_field :title %></div>
<div><%= f.submit %></div>
<% end %>
<%= link_to '戻る', todos_path %>
```
今回原理をよく理解するためにこのようにURLとメソッドもちゃんと書きました。
`#{ }`は文字列として変数を埋め込めるRubyの記法です。
変更するToDoデータの`id`を指定すれば同じことを行えます。
```.erb:edit.html.erb
<h1>ToDo編集</h1>
<%= form_with(model: @todo, url: "/todos/#{@todo.id}", method: :patch) do |f| %>
<div><%= f.label :title %></div>
<div><%= f.text_field :title %></div>
<div><%= f.submit %></div>
<% end %>
<%= link_to '戻る', todos_path %>
```
### 編集ページへのリンクを作る
変更箇所付近だけ抜粋。`link_to`と`~~~~_path`を使ってやるとこんな風に簡単に作れる。
```.erb:index.html.erb
<% @todos.each do |todo| %>
<tr>
<td><%= todo.title %></td>
<td><%= link_to '編集', edit_todo_path(todo) %></td>
</tr>
<% end %>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/4aea333f-90ef-f433-083b-82ea3461eb10.png)
```.erb
<td><%= link_to '編集', edit_todo_path(todo.id) %></td>
```
ついでに、ちゃんと省略して書かないとこういう書き方になる。`todo` → `todo.id`
Railsがいい感じで判断してくれるのでこのようになりますが、原理は覚えておくほうがいいと思います。
htmlだと↓のようなものが作られる
`<a href="/todos/1/edit">編集</a>`
## 削除機能実装
削除機能はわかってしまえば他と大して変わらないので楽です。
1. ルーティング追加
2. コントローラーに`destroy`アクション追加
3. ビューに削除ボタンのリンク追加
```routes.rb
Rails.application.routes.draw do
get '/todos', to: 'todos#index', as: 'todos'
post '/todos', to: 'todos#create'
get '/todos/:id/edit', to: 'todos#edit', as: 'edit_todo'
patch '/todos/:id', to: 'todos#update'
delete '/todos/:id', to: 'todos#destroy'
end
```
- `@todo = Todo.find(params[:id])`で削除する項目(idで指定された項目)を@todoに入れる
- destroyメソッドで削除する
- 元のページに戻る
```app/controllers/todos_controller.rb
def destroy
@todo = Todo.find(params[:id])
@todo.destroy
redirect_to todos_path
end
```
`<td><%= link_to '削除', "/todos/#{todo.id}", method: :delete %></td>`を追加。
`method: :delete`でメソッドを指定。(指定しないとデフォルトのGETになります。)
ルーティングでdeleteに設定したURLは`delete '/todos/:id', to: 'todos#destroy'`なので対応させるために`"/todos/#{todo.id}"`このように書きました。
```.erb:index.html.erb
<% @todos.each do |todo| %>
<tr>
<td><%= todo.title %></td>
<td><%= link_to '編集', edit_todo_path(todo) %></td>
<td><%= link_to '削除', "/todos/#{todo.id}", method: :delete %></td>
</tr>
<% end %>
```
削除機能が完成しました。
![todo3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/a04c517e-eeaa-69c5-6b78-b64ae5a41d04.gif)
見た目は最悪ですが、ToDo登録・更新・削除機能が完成しました。