Rails初心者です。頑張ってます。Ruby on Railsのscaffoldで生成されたコードを全て説明できるようになるようにと言われたので、説明してみます。
bundle exec rails g scaffold Entry title:string body:text
ファイル構成
scaffoldを実行すると以下のファイルが生成されます。その他に生成されるテストコードとかマイグレーションファイルは無視しています。
PROJECT_ROOT
|- controllers
| |- entries_controller.rb
|- helpers
| |- entries_helper.rb
|- models
| |- entry.rb
|- views
| |- _form.html.erb
| |- edit.html.erb
| |- index.html.erb
| |- index.json.jbuilder
| |- new.html.erb
| |- show.html.erb
| |- show.json.jbuilder
コントローラ
まずはコントローラからです。コントローラは空のクラスであるApplicationControllerを継承してます。コントローラの共通処理を定義するためにこのクラスは存在しています。ApplicationControllerは、さらにその親クラスであるActionController::Baseを継承しています。このコードはprotect_from_forgeryメソッドによりCSRF対策をデフォルトで有効にしています。
1
2
3
class ApplicationController < ActionController :: Base
protect_from_forgery with : :exception
end
Entryコントローラにはindex, show, new, edit, create, update, destroyアクションが定義されます。規約によりアクション、URL、HTTPメソッドの対応関係が決まっています。これはRestからの設計思想で以下のようになっています。
アクション
URLパス
HTTP METHOD
index
/entries/
GET
show
/entries/1
GET
new
/entries/new
GET
edit
/entries/1/edit
GET
create
/entries
POST
update
/entries/1
PATCH
destroy
/entries/1
DELETE
そしてこの仕組みを作っているのがルーティングになります。ブロック内でresourcesメソッドにシンボルを渡すことでRailsがRESTアクセスを許可します。exceptや、onlyなどのオプションでアクセスを制御することもできます。
1
2
3
Rails . application . routes . draw do
resources :entries
end
通常はHTTPメソッド 'URLパス', 'コントローラ#アクション'
の形式で定義していきますが、scaffoldが追記したルーティングはリソースベースのルーティングになります。これは以下のルーティング定義と同じになります。
1
2
3
4
5
6
7
8
9
Rails . application . routes . draw do
get 'entries' => 'entries#index'
get 'entries/:id' => 'entries#show'
get 'entries/new' => 'entries#new'
get 'entries/:id/edit' => 'entries#edit'
post 'entries' => 'entries#create'
patch 'entries/:id' => 'entries#update'
delete 'entries/:id' => 'entries#delete'
end
ビューで使用されるルーティング名もここで定義されます。
URLパス
ルーティング名
/entries
:entries
/entries/1
:entry
/entries/new
:new_entry
/entries/1/edit
:edit_entry
あとコントローラのソースをざっと見て感じたことは、scaffoldで生成したコントローラのprivateメソッドは1つインデントを下げているのでコーディング規約的にはこれを覚えた方がいいかなと思います。
1
2
3
4
private
def set_entry
@entry = Entry . find ( params [ :id ] )
end
beforeアクション
各アクションが実行される前に必ず処理されます。アクションで処理を共通化したい場合はここに書きます。これらはshow, edit, update, destroyアクションの前にset_entryというメソッドを実行するよう定義しています。
1
before_action :set_entry , only : [ :show , :edit , :update , :destroy ]
indexアクション
Entryモデルから全件取得し、@entriesというインスタンス変数に代入しているだけです。
1
2
3
index
@entries = Entry . all
end
showアクション
メソッドを定義しているだけで処理内容がありませんが、before_actionが実行されるため処理内容がアクション内になくてもビューにEntryオブジェクトの値が渡ります。
newアクション
Entryモデルのインスタンスを生成して@entryインスタンス変数に渡しています。
editアクション
こちらもメソッドを定義しているだけで実装がありませんが、showアクションと同様、before_actionで処理を任せています。
createアクション
POSTされた値を受け取ってentry_paramsプライベートメソッドに処理をさせます。これは何をやっているかというとStrong Parameterという機能です。たとえばこのentitiesというテーブルにはtitleとbodyといったカラム以外に、created_atなどユーザには表示しないシステムの都合上のカラムなどもあります。不正なアクセスで改ざんされる危険性がありますので、titleとbodyのみを許可するようこのメソッドで処理しています。
1
2
3
def entry_params
params . require ( :entry ) . permit ( :title , :body )
end
こうして許可されたパラメータだけが返ってくるので、そのパラメータを使ってEntryモデルを初期化します。respond_toメソッドのブロックでHTTPレスポンスの処理を行います。formatオブジェクトのhtmlメソッドにはHTMLを返すための処理を定義します、jsonメソッドはJSONを返すための処理を定義します。
問題無くエントリーが保存された場合は、HTMLを取得するためのアクセスならばリダイレクトします。JSONを取得するためのアクセスならばJSONを返します。保存に失敗した場合は、HTMLを取得するためのアクセスならばnewアクションに戻して再入力を施します。JSONを取得するためのアクセスならば、クライアントにJSONでエラー状態のJSONを返します。
saveメソッドは、if文の条件に使われているので成功したらtrue, 失敗したらfalseを返しますが、save!と破壊的メソッドにすれば失敗した場合は例外がraiseされます。
1
2
3
4
5
6
7
8
9
10
11
12
13
def create
@entry = Entry . new ( entry_params )
respond_to do | format |
if @entry . save
format . html { redirect_to @entry , notice : 'Entry was successfully created.' }
format . json { render :show , status : :created , location : @entry }
else
format . html { render :new }
format . json { render json : @entry . errors , status : :unprocessable_entity }
end
end
end
updateアクション
モデルのupdateメソッドを呼ぶところとメッセージ以外、ほぼcreateアクションと同じです。
destroyアクション
モデルのdestroyメソッドを呼び出し、リダイレクトします。ここでredirect_toメソッドの引数でentries_urlというメソッドが呼び出されていますが、このようにルーティングで定義されたルーティング名である:entriesに_urlというサフィックスを付けたメソッドを呼び出すことで/entriesへのURLを生成してくれます。
1
2
3
4
5
6
7
def destroy
@entry . destroy
respond_to do | format |
format . html { redirect_to entries_url , notice : 'Entry was successfully destroyed.' }
format . json { head :no_content }
end
end
モデル
モデルはバリデーションも定義してませんので2行だけです。これだけで多くの機能を使うことができます。
1
2
class Entry < ActiveRecord :: Base
end
ビュー
ビューはRailsのデフォルトではERBが使われます。拡張子が.jbuilderはJSONのビューになりますが、ここでは取り扱わず、erbのみ説明します。
index.html.erb
重要なところは以下の箇所です。コントローラから渡されたエントリー一覧をeachメソッドで繰り返しています。モデルのプロパティを呼び出しながら値を表示します。link_toメソッドはビューのヘルパーメソッドで、第1引数にリンクの文字列、第2引数にURLパスを指定します。showやdestroyの場合はentryオブジェクトを渡すだけでURLパスにしてくれます。editやnewに関してはルーティング名に_pathをつけたヘルパーメソッドがURLパスを出力してくれます。
1
2
3
4
5
6
7
8
9
10
11
< % @entries.each do | entry | %>
<tr>
< td >< %= entry.title %></td>
<td><%= entry . body %></td>
< td >< %= link_to 'Show', entry_path %></td>
<td><%= link_to 'Edit' , edit_entry_path ( entry ) %></td>
< td >< %= link_to 'Destroy', entry, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
<%= link_to 'New Entry' , new_entry_path %>
削除のlink_toはちょっと複雑です、第2引数にentryオブジェクトを渡すのはshowアクションへのリンクと同じですが、第3引数にハッシュを渡しています。method: :delete
と定義することでdata-method="delete"
という属性を出力します。この指定をすることでRailsはリンクをGETではなくDELETEでアクセスします。data: { conform: 'Are you sure?' }
と定義することでdata-confirm="Are you sure?"
という属性を出力します。これにより削除のリンクをクリックするとconfirmダイアログが出力されます。
show.html.erb
このビューも重要なのは以下の箇所です。ルーティング名である:entries, :edit_entryに_pathをつけたヘルパーメソッドによりURLパスを指定しています。
1
2
< %= link_to 'Edit', edit_entry_path(@entry) %> |
<%= link_to 'Back' , entries_path %>
new.html.erb
ビューでのrenderヘルパーメソッドは引数に文字列を指定すると、_指定した文字列.html.erb
のファイルを読み込みます。フォームの内容は_form.html.erb
に記述されています。
1
2
3
< %= render 'form' %>
<%= link_to 'Back' , entries_path %>
edit.html.erb
new.html.erb
とほぼ一緒です。
1
2
3
4
< %= render 'form' %>
<%= link_to 'Show' , @entry %> |
<%= link_to 'Back', entries_path %>
_form.html.erb
フォームヘルパーにエントリーオブジェクトを渡してブロックでフォーム内容を定義していきます。最初にあるのはエラー処理になります。フォームを作成するには、フォームオブジェクトのヘルパーメソッドにモデルの属性をシンボルを渡していけばHTMLでフォームを出力します。フォームヘルパーはひとつずつ覚えていくしかないと思います。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
< %= form_for(@entry) do |f| %>
<% if @entry.errors.any? %>
<div id= "error_explanation" >
< h2 >< %= pluralize(@entry.errors.count, "error") %> prohibited this entry from being saved:</h2>
<ul>
<% @entry.errors.full_messages.each do |message| %>
<li><%= message %></li>
< % end %>
</ul>
< /div>
<% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</ div >
< div class = "field" >
< %= f.label :body %><br>
<%= f . text_area :body %>
</div>
< div class = "actions" >
<%= f . submit %>
</div>
< % end %>
まとめ
1行1行説明することで理解ができました。基本をやりなさいと言われた時、「それぐらい分かる」と思いがちで実際に素直にやらない場合もあるかもしれませんが、経験的にこの辺の基礎的なことは、もうバカみたいに実直にやった方がいいと思います。仕組みが分かったので、後はscaffoldを何度か写経したら完璧です。