DIVE INTO CODE

プロのエンジニアになるために挑戦する人が、チャンスをつかめる場をつくる。

DIVE03

DIVE03のゴール

DIVE03は、ブログ投稿確認画面を実装しながらRailsの基礎について学んでいきます。

ブログ画面の遷移

確認画面を実装することで、ブログを投稿する前に、投稿するか、戻って編集するかどうかを選択できるようになります。

https://diveintocode.gyazo.com/dbb7a500c0dde41e946323c9b430985b

確認画面を実装する前に確認画面のアルゴリズムについて確認しましょう。

https://diveintocode.gyazo.com/75617dbcb531ccea1ddc7ca29b575f0e

現在、achieveのお問い合わせ機能は以下のような画面遷移になっています。

入力画面 => 完了画面

しかしお問い合わせ機能は以下のように入力した内容を確認する画面を経由することが一般的です。

入力画面 => 確認画面 => 完了画面

ここからは確認画面を作成し、Railsの基礎を学んでいきましょう。

確認画面を作成する

まずnewアクションで送信ボタンを押すと、確認画面にうつり確認画面で送信を押すと、ブログが作成されるというシンプルな機能を実装します。
戻るボタンは後ほど作成します。

confirmアクションのルーティングを定義する

resourcesメソッドにconfirmは含まれていないので、collection, memberを用いてルーティングを定義していきます。

:pencil2: config/routes.rb にcollectionでconfirmのルーティングを定義しましょう。

collection do ~ end の箇所が今回追加するルーティング定義です。

config/routes.rb

1
2
3
4
5
6
7
8
Rails.application.routes.draw do
  resources :blogs, only: [:index, :new, :create, :edit, :update ,:destroy] do
    collection do
      post :confirm
    end
  end
  省略
end

:pencil2: rake routesで定義したルーティングを確認しましょう。

lasershow:~/workspace/achieve $ rake routes
       Prefix Verb   URI Pattern               Controller#Action
confirm_blogs POST    /blogs/confirm(.:format)  blogs#confirm
        blogs GET    /blogs(.:format)          blogs#index
              POST   /blogs(.:format)          blogs#create
     new_blog GET    /blogs/new(.:format)      blogs#new
    edit_blog GET    /blogs/:id/edit(.:format) blogs#edit
         blog PATCH  /blogs/:id(.:format)      blogs#update
              PUT    /blogs/:id(.:format)      blogs#update
              DELETE /blogs/:id(.:format)      blogs#destroy

(参考)
memberのルーティングは、idを含んだ定義となります。

1
2
3
4
5
6
7
8
Rails.application.routes.draw do
  resources :blogs, only: [:index, :new, :create, :edit, :update ,:destroy] do
    member do
      post :confirm
    end
  end
  省略
end
1
2
3
4
5
6
7
8
9
10
11
lasershow:~/workspace/achieve (day3) $ rake routes
      Prefix Verb   URI Pattern                  Controller#Action
confirm_blog POST   /blogs/:id/confirm(.:format) blogs#confirm
       blogs GET    /blogs(.:format)             blogs#index
             POST   /blogs(.:format)             blogs#create
    new_blog GET    /blogs/new(.:format)         blogs#new
   edit_blog GET    /blogs/:id/edit(.:format)    blogs#edit
        blog PATCH  /blogs/:id(.:format)         blogs#update
             PUT    /blogs/:id(.:format)         blogs#update
             DELETE /blogs/:id(.:format)         blogs#destroy
省略

(参考)
ルーティング定義は、collection, memberを用いなくても以下のように記載できます。
しかし、URIパターンを記載する必要があるためルーティング定義の管理が難しくなります。

1
post 'blogs/confirm' => blogs#confirm

:pencil2: app/controllers/blogs_controller.rbにconfirmアクションを定義しましょう。

confirmアクション内の処理は定義しなくて大丈夫です。

confirm Viewを用意する

確認画面で求められるのは、その場で編集することではなく、入力した値を確認することです。
例えば、new Viewのような実装をしてしまうとその場で編集できるようになってしまいます。

:pencil2: app/views/blogs/_form.html.erbの定義を確認してみましょう。

_form.html.erbは、form_forでf.text_fieldを使用して、フォームを生成しています。
そのためフォームに入力することができ、値を変更できてしまいます。

app/views/blogs/_form.html.erb

1
2
3
4
5
6
7
8
9
10
11
<%= form_for(@blog) do |f| %>
 省略

  <%= f.label :title %>
  <%= f.text_field :title %>
  <br>
  <%= f.label :content %>
  <%= f.text_field :content %>
  <br>
  <%= f.submit %>
<% end %>

:pencil2: app/views/blogs/confirm.html.erb を編集する。

そこで、f.hidden_fieldを使用して、入力フォームを生成せず、new Viewからきた値をそのまま、createアクションに渡すようにします。
また、値を表示させるために、変数をそのまま出力するようにします。

app/views/blogs/confirm.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
<h4>以下の内容で、送信する。</h4>

<%= form_for(@blog) do |f| %>
  <!-- `f.hidden_field`を使用して、リクエストパラメータに値を設定できるようにしています。 -->
  <%= f.hidden_field :title %>
  <%= f.hidden_field :content %>

  <!-- @blog.titleと@blog.contentとすることで、表示用の値を画面に出力しています。 -->
  <p>タイトル:<%= @blog.title %></p>
  <p>本文:<%= @blog.content %></p>
  <%= f.submit "登録する" %>
<% end %>

Check Point !!

`hidden_field`によりpostメソッド実行時に`form_for do ~ end`に設定した値をリクエストパラメータに設定できる。

form_forに渡す変数を指定する

confirm Viewで使用する@blogを定義しましょう。
@blogには、new Viewで入力されてきた値を取得するようにします。
入力された値は、parametersで送られてくるので、createメソッド同様に、paramsメソッドとストロングパラメータを使用して値を取得します。

:pencil2: blogs_controller.rb にてリクエストパラメータから@blog変数を設定する。

リクエストパラメータ(blog_params)からnewメソッドを使用して@blog変数を設定しましょう。

app/controllers/blogs_controller.rb

1
2
3
  def confirm
    @blog = Blog.new(blogs_params)
  end

newアクションからconfirmアクションに移動するようにする

現在は、newアクションからcreateアクションを実行しています。
これは,new Viewのform_forが指定しているからです。

form_forはデフォルトで、引数によってどのようなURLで送信するかが決まっています。

  • 引数がレコードに存在しない場合、createアクションへ送信します。
  • 引数がレコードに存在している場合、updateアクションへ送信します。

https://diveintocode.gyazo.com/7fd75690ff65428f4c985004f75cc04c

従って、cofirmアクションへ送信するためは、オプションを与えて、送り先を変える必要があります。

:pencil2: app/views/blogs/_form.html.erb のform_forの中身を編集する。

url: confirm_blogs_pathurlオプションを与えることで、blogs#confirmを実行するように変更しています。

app/views/blogs/_form.html.erb

1
2
3
<%= form_for(@blog, url: confirm_blogs_path) do |f| %>
・
・

実装確認する

:pencil2: newアクション => confirmアクション => createアクションと遷移できるか確認しましょう。

確認画面を実装することができていれば、ブログを作成する前にこのような画面を経由することができます。

https://diveintocode.gyazo.com/23b2714a1b2cd74470c95ba4071747c0

newアクションだけ確認画面を経由するようにしよう。

作成画面の場合は確認画面を経由できるようになりましたが、ブログを編集しようとすると、このようなエラーが発生します。

https://diveintocode.gyazo.com/ee3096a65deae5e29f7f9541c68d329d

さらにログを確認すると以下のようになっているはずです。

Started GET "/blogs/27/edit" for 203.136.253.36 at 2016-04-13 05:48:15 +0000
Cannot render console from 203.136.253.36! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by BlogsController#edit as HTML
  Parameters: {"id"=>"27"}
  Blog Load (0.6ms)  SELECT  "blogs".* FROM "blogs" WHERE "blogs"."id" = $1 LIMIT 1  [["id", 27]]
  Rendered blogs/_form.html.erb (6.2ms)
  Rendered blogs/edit.html.erb within layouts/application (10.2ms)
Completed 200 OK in 193ms (Views: 52.3ms | ActiveRecord: 0.6ms)


Started PATCH "/blogs/confirm" for 203.136.253.36 at 2016-04-13 05:48:17 +0000
Cannot render console from 203.136.253.36! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by BlogsController#update as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"weqRqSrPR+T6jPkmQDwM8LtThnPO82bBAPZ+kbTyNB63oVNcrPt+Mk7YUJi3CGm0tvrPedY4Win9i3HSIHOmKg==", "blog"=>{"title"=>"あああ", "content"=>"あああああ"}, "commit"=>"Update Blog", "id"=>"confirm"}
  Blog Load (0.5ms)  SELECT  "blogs".* FROM "blogs" WHERE "blogs"."id" = $1 LIMIT 1  [["id", 0]]
Completed 404 Not Found in 6ms (ActiveRecord: 0.5ms)

Started GET "/blogs/27/edit"editアクションが起動した後に、Started PATCH "/blogs/confirm"が起動しているのがわかるかと思います。
しかし、PATCH "/blogs/confirmというroutingは存在しません。updateアクションへ送信するためには、PATCH /blogs/:id(.:format) blogs#updateと送る必要があります。

従って、newアクションの時は、url: confirm_blogs_pathオプションに、updateアクションのときはblogs_pathオプションが生成されるようにしましょう。

ヘルパーメソッドを定義する

アクションで分岐したい場合、action_nameを使用すると簡単に実装できます。
例えば

1
2
3
if action_name == 'new'
  puts 1
end

以上の場合newアクションのときだけに、「1」と表示されます。

urlの生成を分岐させる場合、

app/views/blogs/_form.html.erb

1
<%= form_for(@blog, if action_name == 'new' url: confirm_blogs_path else blog_path end) do |f| %>

としても良いのですが、これでは分かりにくくなってしまうので、事前にヘルパーメソッドとして定義します。

ヘルパーメソッドとは

ヘルパーファイルに定義するメソッドをヘルパーメソッドと言います。
ヘルパーメソッドに定義したメソッドは、どのビューからも使用できるようになります。

そこで、newアクションとeditアクションの時に適切なpathを返すヘルパーメソッドを定義しましょう。

:pencil2: app/helpers/blogs_helper.rbにhelperメソッドを作成する。

blogs_helper.rb にchoose_new_or_editメソッドを定義してください。
action_nameを使用することでnewアクションの時はconfirm_blogs_pathを、editアクションの場合はblogs_pathを返します。

app/helpers/blogs_helper.rb

1
2
3
4
5
6
7
8
9
module BlogsHelper
  def choose_new_or_edit
    if action_name == 'new' || action_name == 'confirm'
      confirm_blogs_path
    elsif action_name == 'edit'
      blog_path
    end
  end
end

action_name == 'new' || action_name == 'confirm'
とすることで、アクションが、newまたは、confirmの時にのみtrueを返却します。

定義したヘルパーメソッドを使用する

ヘルパーメソッドを使用するためには、メソッド名を記述する必要があります。

:pencil2: app/views/blogs/_form.html.erbのform_forの中をhelperメソッドに置き換える。

app/views/blogs/_form.html.erb

1
2
3
<%= form_for(@blog, url: choose_new_or_edit) do |f| %>
・
・

これで、newアクションの時は確認画面を経由、editアクションの時は経由せずに更新できるようになりました。

戻るボタンを作成する

:pencil2: 確認画面から入力画面に戻れるようにしましょう。

前のアクションに戻るためには、link_to メソッドの引数に:backを与えると実現できます。

app/views/blogs/confirm.html.erb

1
2
3
4
5
6
7
8
9
10
11
<h4>以下の内容で、送信する。</h4>

<%= form_for(@blog) do |f| %>
  <%= f.hidden_field :title %>
  <%= f.hidden_field :content %>
  <p>タイトル:<%= @blog.title %></p>
  <p>本文:<%= @blog.content %></p>
  <%= f.submit "登録する" %>
<% end %>

<%= link_to '戻る', :back %>

https://diveintocode.gyazo.com/19cc94bc967c97d29b4af78f7f28bf4d

しかし、link_toでは、デフォルトのHTTPメソッドがgetであるためControllerにパラメータを渡せません。
そのため前画面に戻るとパラメータは初期化されてしまいます。
パラメータが初期化されないように戻るボタンを作成するには、form_forを使用します。

Check Point !!

urlオプションによりform_forで実行をリクエストするアクションを指定することができる。

form_forで戻るボタンを作成する

:pencil2: app/views/blogs/confirm.html.erb に戻るボタン用のform_forを作成する。

登録するボタンと戻るボタンで別々のフォームを設定するように定義します。
戻るボタンのform_forはnewアクションを実行リクエストするため、HTTPメソッドがgetになるので注意してください。

app/views/blogs/confirm.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<h4>以下の内容で、送信する。</h4>

<%= form_for(@blog) do |f| %>
  <%= f.hidden_field :title %>
  <%= f.hidden_field :content %>
  <p>タイトル:<%= @blog.title %></p>
  <p>本文:<%= @blog.content %></p>
  <%= f.submit "登録する" %>
<% end %>

<%= form_for(@blog, url: new_blog_path, method: 'get') do |f| %>
  <%= f.hidden_field :title %>
  <%= f.hidden_field :content %>
  <%= f.submit "もどる" %>
<% end %>

これで値をparametersで送ることができる、戻るボタンを実装することができました。

:pencil2: 戻るボタンを実行時のログを見てリクエストパラメータの内容を確認してみましょう。

Started GET "/blogs/new?utf8=%E2%9C%93&blog%5Btitle%5D=%E3%81%82%E3%81%82%E3%81%82%E3%81%82&blog%5Bcontent%5D=%E3%81%82%E3%81%82%E3%81%82&commit=%E3%82%82%E3%81%A9%E3%82%8B" for 203.136.253.36 at 2016-04-13 08:38:07 +0000
Cannot render console from 203.136.253.36! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by BlogsController#new as HTML
  Parameters: {"utf8"=>"✓", "blog"=>{"title"=>"ああああ", "content"=>"あああ"}, "commit"=>"もどる"}
  Rendered blogs/_form.html.erb (3.9ms)
  Rendered blogs/new.html.erb within layouts/application (6.5ms)
Completed 200 OK in 50ms (Views: 48.5ms | ActiveRecord: 0.0ms)

きちんとパラメータが送られているのが確認できるかと思います。
Parameters: {"utf8"=>"✓", "blog"=>{"title"=>"ああああ", "content"=>"あああ"}, "commit"=>"もどる"}

newアクションでパラメータを元にインスタンスを作成されるようにする

parametersを送ることができましたが、以前値は初期化されます。
値を初期化するためには、送られてきたパラメータをもとにnewメソッドを実行する必要があります。

@blog = Blog.new(blog_params)とすれば、値を保持したまま戻ることができるはずです。

そこで、戻るボタンを押して、newアクションを実行した場合のみ、@blog = Blog.new(blog_params)と実行されるようにします。

戻るボタンにname属性を追加する

:pencil2: app/views/blogs/confirm.html.erb のsubmitにname属性を付ける。

戻るボタンで実行されたnewアクションであることを示すために、戻るボタンからであるとわかるようなparametersを追加します。

app/views/blogs/confirm.html.erb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<h4>以下の内容で、送信する。</h4>

<%= form_for(@blog) do |f| %>
  <%= f.hidden_field :title %>
  <%= f.hidden_field :content %>
  <p>タイトル:<%= @blog.title %></p>
  <p>本文:<%= @blog.content %></p>
  <%= f.submit "登録する" %>
<% end %>

<%= form_for(@blog, url: new_blog_path, method: 'get') do |f| %>
  <%= f.hidden_field :title %>
  <%= f.hidden_field :content %>
  <%= f.submit "もどる", name: 'back' %>
<% end %>

<%= f.submit "もどる", name: 'back' %>とすることで、parametersにname: 'back'を追加することできます。

実際に戻るボタンを押してみると、ログはこのように表示されます。

1
  Parameters: {"utf8"=>"", "blog"=>{"title"=>"ああ", "content"=>"ああ"}, "back"=>"もどる"}

parametersを元に処理を分岐させる

これでparametersに確認画面の戻るボタン用のparametersを追加することができたので、
このparametersを元にnewアクションを分岐します。

:pencil2: blogs_controller.rbに条件を分岐を作る。

app/controllers/blogs_controller.rb

1
2
3
4
5
6
7
  def new
    if params[:back]
      @blog = Blog.new(blogs_params)
    else
      @blog = Blog.new
    end
  end

これで、値が初期化されない戻るボタンを作成することができました。

確認画面に移る前に、バリデーションが発生するようにする。

https://diveintocode.gyazo.com/61391b0924245f8b2e5c3d5083fbeba9

現在のブログ機能では、タイトルが入力されていない(バリデーションで禁止している)のにも関わらず、確認画面に移動することができてしまいます。
これは、バリデーションが発生するタイミングが値が保存される時であるためです。

そこで確認画面に移る前に、手動でバリデーションを発生させて、失敗した場合は投稿画面に戻るようにしましょう。
バリデーションを発生させるためには、validメソッドかinvalidメソッドを使用します。valid?/invalid?メソッドは、バリデーションを実行し、失敗したらfalse/trueを返します。
その性質をいかして、バリデーションに失敗した場合投稿画面にもどすようにします。

:pencil2:blogs_controller.rbにバリデーションの際の条件分岐を作成する。

app/controllers/blogs_controller.rb

1
2
3
4
  def confirm
    @blog = Blog.new(blogs_params)
    render :new if @blog.invalid?
  end

Check Point !!

ブログ名.invalid?はバリデーションを実行し、boolean型の戻り値を受け取ります。
  バリデーションが失敗 => true
  バリデーションが成功 => false

これで、確認画面に移る前にバリデーションを実行することができました。
ブログ機能はこれで完成となります。

コラム

:pencil2: クエリストリングから情報が漏れる

HTTPリクエストのGETメソッドでのパラメータの受け渡しは、http://ドメイン?パラメータ名=値の形式で行いますが、

この「?パラメータ名=値」をクエリストリングと言います。

クエリストリングは、Webページ間で手軽にパラメータを受渡しできる方法ですが、ユーザの目に触れやすく改ざんが容易にできてしまいます。

クエリストリングには、決して重要な情報を含めないようにしましょう。

一般的に、クエリストリングは次のような用途の場合に使用されます。

  • ユーザが入力したパラメータの送信
  • Webページ間のパラメータ受け渡し
  • Webアプリケーションへのパラメータ付きリンク

ユーザがWebページ上のフォーム項目で入力したパラメータは、フォームの送信方法がGETの場合は、必ずクエリストリングに乗せてWebアプリケーションへ送られます。

一方、POSTとした場合にはパラメータの送信にクエリストリングは使用されません。

POST の場合はパラメータはURL上ではなく、HTTPリクエストのボディ情報として送信されます。

なお、POSTパラメータChromeなどWebブラウザのディベロッパーツールで確認することができます。

https://diveintocode.gyazo.com/96c4b62649a1dcb3e879cbedbe5550fd

DIVE03課題

お問い合わせに確認画面と「戻る」ボタンを実装してください。

・ 「戻る」ボタンを押した後に表示される入力フォーム上で値は初期化されないものとする。
・ 確認画面に移る前にバリデーションが実行され、失敗した場合投稿画面に戻るようにする。

課題に取り組んでみましょう。ご質問等ありましたら無料説明会で承ります。

DIVE INTO CODEの無料説明会を開催中

プロのエンジニアになるために挑戦する人を応援します.

無料説明会はこちら