console.log(‘blog’) | puts ‘blog’

JavascriptとかRubyとか

Railsのscaffoldで生成されたコードを1行1行説明する

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を何度か写経したら完璧です。