TechAcademy Webアプリケーションコース(19日目)

Controller と View

ControllerとViewは密接に関わり同時に開発していく。
Router↘
   Controller ↔ Model
View↙
まずは Router の次にある Controller から作成。

Controller の生成
$ rails g controller コントローラ名 Controller の生成コマンド
テックアカデミーのカリキュラムでは、
$ rails g controller Messages --no-helper --no-assets でコマンド入力
このログから、 app/controllers/messages_controller.rb(ファイル) と app/views/messages(ディレクトリ) が生成される。
invoke erb は ERB 関連という意味で、 app/views/messages は ERB 関連
app/controllers/messages_controller.rb のなかみには
class MessagesController < ApplicationController
end
Model 同様にクラスを継承しているだけのコード
元をたどれば基本機能が提供されている。

・ControllerをRESTfulnaなルーティングに対応させる
config/routes.rb で設定したルーティングに対応したアクションを messages_controller.rb に追加する

      Prefix Verb   URI Pattern                  Controller#Action
   messages GET    /messages(.:format)          messages#index
            POST   /messages(.:format)          messages#create
new_message GET    /messages/new(.:format)      messages#new
edit_message GET    /messages/:id/edit(.:format) messages#edit
    message GET    /messages/:id(.:format)      messages#show
            PATCH  /messages/:id(.:format)      messages#update
            PUT    /messages/:id(.:format)      messages#update
            DELETE /messages/:id(.:format)      messages#destroy

app/controllers/messages_controller.rb

class MessagesController < ApplicationController
 def index
 end
 def show
 end
 def new
 end
 def create
 end
 def edit
 end
 def update
 end
 def destroy
 end
end

Controller 内でルーティングと同じ名前のメソッド名として定義すれば、7つのアクションを全て対応させました。しかしまだアクション(メゾット)定義の中に何も書かれておらず、何もしません。

View 構成について
7つのアクションに対応した View ファイルの作成
まず7つのアクションに対応した View ファイル。ただしルーティングの中で必要なのは GET メソッドで指定されたルーティングのみ。なぜなら GET 以外はページの表示ではなくリソースの具体的な操作だからです。

app/views/messages/index.html.erb
app/views/messages/show.html.erb
app/views/messages/new.html.erb
app/views/messages/edit.html.erb

GET のルーティングである index, show, new, edit に対応する View を作成。
$ rails g controller Messages --no-helper --no-assets のコマンドによって app/views/messages/ フォルダは作成されており、その直下に上記4つのファイルを各自で作成。View は app/views/ の下にフォルダ分けされながら配置される。残念ながら View を作成する rails generate コマンドは用意されてないので手作業で1つずつ作成する。
index.html.erb のように .erb だけでなく、.html と入れているのは、 ERB ファイルが HTML ファイルへと変換されることを Rails に対して明示するため。

共通部の抜き出し
View(ERB) は、基本的には HTML を記述していく。
共通部(<!DOCTYPE html> や head内)は1つのファイルにまとる。Rails であれば手軽にできる。
既にある app/views/layouts/application.html.erb を開く。

<!DOCTYPE html>
<html lang="ja">
 <head>
   <title>MessageBoard</title>
   <%= csrf_meta_tags %>
   <%= csp_meta_tag %>
   <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
   <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
 </head>
 <body>
   <%= yield %>
 </body>
</html>

csrf,csp はセキュリティ対策
stylesheet,favascript は今回使わない

重要なのは、body要素内の <%= yield %> 

yield という Ruby コードによって、body要素内に埋め込まれるページが切り替わる。<%= yield %> があるところ(つまりbody要素内)に index.html.erb や show.html.erb などの各ページの内容が代入される。<%= yield %> のおかげで、body要素内以外は共通化され、body要素内はアクセスされたページ毎のコンテンツが切り替えられて表示される。

messages#index
7つのルーティングに対するレスポンスの実装していきます。
まずは index アクションに対応する Controller と View を開発。

Controller
app/controllers/messages_controller.rb の index アクション
index アクションの役割は、Message モデルのレコードの一覧表示。

app/controllers/messages_controller.rb の index アクション

  def index
   @messages = Message.all
 end

Message.all で Message のレコードを全て取得していて、インスタンス変数である @messages に代入。
@messages に代入するのは、次に View 側 (index.html.erb) で @messages を使用するため。

View
次は index アクションに対応する View を編集。Controller から渡されたデータ (@messages) を一覧表示させる。

app/views/messages/index.html.erb

<h1>メッセージ一覧</h1>
<ul>
 <% @messages.each do |message| %>
   <li><%= message.content %></li>
 <% end %>
</ul>

Controller から渡された @messages というレコード群を、 @messages.each で1つずつ message として取り出し、 <%= message.content %> によって、content カラムの値を表示。

動作確認
index の表示が正常に行われるかを動作確認
Rails サーバを起動($ rails s )して、 index のルーティングにアクセス(Preview)


messages#show
index アクションとやることは似たような流れ・コーディングする。

Controller
app/controllers/messages_controller.rb の show アクション

  def show
   @message = Message.find(params[:id])
 end

index アクションのときは Message.all でレコード全てを取得していたが、
今回は id が指定されているので Message.find(params[:id]) によって1つだけ取得する。@message 変数も単数形の命名がされる。

View
View には @message というレコード1件が届けられているので、それを使用して表示を行う。
app/views/messages/show.html.erb

<h1>id: <%= @message.id %> のメッセージ詳細ページ</h1>
<p><%= @message.content %></p>

動作確認
これで /messages/1 や /messages/2 など、レコードに id に対してアクセスができる。

index から show へのリンクを追加する
今のままでは、 show には URL を直打ちしなければアクセスできません。そのため、 index の View に show へのリンクを追加しましょう。

app/views/messages/index.html.erb

<h1>メッセージ一覧</h1>
<ul>
 <% @messages.each do |message| %>
   <li><%= link_to message.id, message %> : <%= message.content %></li>
 <% end %>
</ul>

link_to は Rails が提供する便利メソッドで、
link_to 表示文字列, リンク先 という形で記述します。
表示文字列 は message.id つまりメッセージの id であり、リンク先 は message となっています。
省略せずに書くと message_path(message) となり、リンク先 にこう記述しても正常に動作します。 message も message_path(message) もどちらも最終的に /messages/1 など /messages/:id の形の URL を生成しているだけです。

リンク生成のためのメソッド
$ rails routes によって得られるルーティングに Prefix として messages, new_message などが載せられています。

      Prefix Verb   URI Pattern                  Controller#Action
   messages GET    /messages(.:format)          messages#index
            POST   /messages(.:format)          messages#create
new_message GET    /messages/new(.:format)      messages#new
edit_message GET    /messages/:id/edit(.:format) messages#edit
    message GET    /messages/:id(.:format)      messages#show
            PATCH  /messages/:id(.:format)      messages#update
            PUT    /messages/:id(.:format)      messages#update
            DELETE /messages/:id(.:format)      messages#destroy

ルーティングを設定すると、自動的にリンク生成のためのメソッドも定義されます。上記のようなルーティングが生成された場合には、下記の表のようにメソッドが生成されます。

ルーティング :リンク生成メソッドの使用例: 生成されるリンク
index: messages_path :/messages
show: message_path(@message): /messages/1 など
new :new_message_path :/messages/new
edit :edit_message_path(@message): /messages/1/edit など

見事に、Prefix + _path となっている。また、:id が必要なものはインスタンスを特定する必要があるので引数として @message などのインスタンスが必要。今後もこの法則で自動生成されるメソッド(prefix_path)を使って、リンクを生成していく。

・messages#new
new アクションは、 POST メソッドを送信する新規作成用の入力フォーム置き場になります。

Controller
app/controllers/messages_controller.rb の new アクション

  def new
   @message = Message.new
 end

Message モデルのためのフォームなので、フォームの入力項目のために @message = Message.new でインスタンスを作成しています。

View
app/views/messages/new.html.erb

<h1>メッセージ新規作成ページ</h1>
<%= form_with(model: @message, local: true) do |f| %>
 <%= f.label :content, 'メッセージ' %>
 <%= f.text_field :content %>
 <%= f.submit '投稿' %>
<% end %>
<%= link_to '一覧に戻る', messages_path %>

まず、 form_with でフォームを開始して、 end でフォームを終了。
form_with(model: @message) のように、Controller の new アクションで用意した @message を使用してフォームを作成することを明示。
local: true 今回はサイト内での遷移であること、ならびに画面遷移を伴う同期通信のみで構わないため local: true をつけています。
フォームの入力欄は、form_with ブロックの中に記述します。f.label, f.text_field でカラムを指定して、@message の中のどのカラムに対する入力欄なのかを明示します。
最後に、f.submit で送信ボタンを生成します。

これでフォームは完成です。一度、ブラウザ messages/new にアクセスしてフォームが表示されるか確認。

index から new へのリンクを追加する

<h1>メッセージ一覧</h1>

<ul>
 <% @messages.each do |message| %>
   <li><%= link_to message.id, message %> : <%= message.content %></li>
 <% end %>
</ul>
<%= link_to '新規メッセージの投稿', new_message_path %>

<%= link_to '新規メッセージの投稿', new_message_path %> を追加して、 index から new へのリンクを作成します。

・8.7 messages#create
create アクションでは、 new のページから送信されるフォームを処理することになります。

Controller
new から create へ送られてきたフォームの内容は params[:message] に入っています。しかし、 params[:message] をそのまま使用するのは、セキュリティの観点から推奨されません。直接 params[:message] を扱うのではなく、Strong Parameter(ストロングパラメータ)というフィルタを使用します。

app/controllers/messages_controller.rb の create アクション

def create
@message = Message.new(message_params)

   if @message.save
     flash[:success] = 'Message が正常に投稿されました'
     redirect_to @message
   else
     flash.now[:danger] = 'Message が投稿されませんでした'
     render :new
   end
 end
 def edit
 end
 def update
 end
 def destroy
 end
 private
 # Strong Parameter
 def message_params
   params.require(:message).permit(:content)
 end

まず、コード下部の private は、それ以降に定義されたメソッドがアクションではなく、このクラス内でのみ使用することを明示しています。そのため、def message_params は、アクションではなく単なる私用のメソッドとなります。
def message_params が Strong Parameter です。ちゃんと必要なパラメータを把握して、送信されてきたデータを精査(フィルタリング)しようということです。今回の場合、 :content カラムだけが欲しいデータです。それ以外のデータはフィルタにかけて捨てるようにします。params.require(:message) で Message モデルのフォームから得られるデータに関するものだと明示し、.permit(:content) で必要なカラムだけを選択しています。
コード上部に戻り、 @message = Message.new(message_params) に注目しましょう。Message のインスタンスを生成するときに、Strong Parameter が使用されていることを確認してください。これにより Message.new(content: '...') となることが担保されます。
次に、if @message.save としています。これは if 文による条件分岐と、@message の保存を同時に行っています。@message.save は成功すると true を返し、失敗すると false を返します。そのため、成功した場合と失敗した場合で、処理を分岐させています。
今回の Message 投稿が正常にできたことを、ユーザにお知らせとしてページ上に表示するためのものです。後ほど、これを表示するための View を作成します。次は、redirect_to @message となっています。redirect_to はリンク先を指定して強制的に飛ばすメソッドです。ここでは、リンク先は @message となっていて、これは link_to と同様で、@message の show ルーティング(messages/:id)に飛ばします。つまり、メッセージが保存できたらその show ページに飛ばします。そして flash[:success] によって保存が成功したことをお知らせするということです。
最後に保存が失敗した場合に実行されるコードを確認します。flash.now[:danger] = 'Message が投稿されませんでした' は先ほどと同様です。render :new は、 messages/new.html.erb を表示するということです。そもそも create アクションに対応した View ファイルが無いので、なんらかの View ファイルを表示する必要がありました。そのため、保存が失敗したときには messages/new.html.erb を表示するように指定しています。また、このとき注目したいのが、たとえ保存に失敗しても、 @message = Message.new(message_params) によって @message インスタンスは残っており @message.content には値が入っているので、 new.html.erb 内のフォームにある form_with(model: @message, local: true) 内の content の入力欄にはたった今失敗した文字列値が予め入力された状態になるところです。

redirect_to と render の違い
redirect_to @message は、 処理を messages#show のアクションへと強制的に移動させるもので、create アクション実行後に更に show アクションが実行され、show.html.erb が呼ばれます。render :new は、単に messages/new.html.erb を表示するだけです(messages#newのアクションは実行しない)。

View
create アクションはメッセージを新規作成したあと、 /messages/:id へリダイレクトさせているので、View は不要です。保存に成功すれば、show.html.erb が表示され、保存に失敗すれば、new.html.erb が表示されます。なお、ここで、show から index に戻れるよう、「一覧に戻る」リンクを追加しています。
app/views/messages/show.html.erb

<h1>id: <%= @message.id %> のメッセージ詳細ページ</h1>
<p><%= @message.content %></p>
<%= link_to '一覧に戻る', messages_path %>

また、フラッシュメッセージ(flash)を表示するために、ファイルを作成します。
app/views/layouts/_flash_messages.html.erb

<% flash.each do |message_type, message| %>
 <div><%= message %></div>
<% end %>

そして、これを全ての View で表示させたいので、application.html.erb で render します。こうすることで、render と書いた場所に _flash_messages.html.erb の記述内容が埋め込まれます。application.html.erb のコードをスッキリさせるための工夫です。この _flash_messages.html.erb のように名前が _ からはじまるファイルを作成し、 View の一部を抜き出して記述したものをパーシャルと呼びます。

app/views/layouts/application.html.erb の body

  <body>
   <div class="container">
     <%= render 'layouts/flash_messages' %>
     <%= yield %>
   </div>
 </body>

これで、flash に代入されるとメッセージが表示されるようになります。

flash について
flash は、Controller から View へメッセージを渡すときの変数であり、ハッシュです。
app/controllers/messages_controller.rb の create アクション

  def create
   @message = Message.new(message_params)
   if @message.save
     flash[:success] = 'Message が正常に投稿されました'
     redirect_to @message
   else
     flash.now[:danger] = 'Message が投稿されませんでした'
     render :new
   end
 end

flash[:success] = 'Message が正常に投稿されました' は、 @message.save が成功したときに実行されます。flash はハッシュなので、 { success: 'Message が正常に投稿されました' } という形で保存されます。これが Controller (MessagesController) から View (app/views/layouts/_flash_messages.html.erb) へ受け渡され、表示されます。

app/views/layouts/_flash_messages.html.erb

<% flash.each do |message_type, message| %>
 <div><%= message %></div>
<% end %>

上記は、flash に代入された全てのメッセージが1つ1つ取り出されて、全て表示されます。ハッシュであるため、 each で取り出すと、 |key, value| のペアで取り出されます。今回は |message_type, message| という変数名を用いています。@message.save が成功した場合、 message_type には :success が代入され、message に 'Message が正常に投稿されました' が代入され、message のみ表示されます。

また、なぜ :success としているかというと、次のレッスンの Bootstrap で明らかになります。結論を先に言っておくと、success という文字列を取得してメッセージに緑色の背景を付けるためです。同様に danger は、Bootstrap によってメッセージに赤色の背景をつけます。

flash と flash.now の違い
いつ使用するかの基準としては、 redirect_to の前なら flash であり、render の前なら flash.now です。
理由は、redirect_to は、HTTP リクエストを発生させるので flash.now だと内容を保持できずに消えてしまうし、render は HTTP リクエストを発生させないので flash.now が消えないからです。

・messages#edit
edit アクションは new アクションと似ています。しかし、既存のメッセージレコードを編集することになるので、 id でメッセージレコードを検索することになります。params[:id] による検索は show アクションと全く同じです。

Controller
app/controllers/messages_controller.rb の edit アクション

  def edit
   @message = Message.find(params[:id])
 end

View
app/views/messages/edit.html.erb

<h1>id: <%= @message.id %> のメッセージ編集ページ</h1>
<%= form_with(model: @message, local: true) do |f| %>
 <%= f.label :content, 'メッセージ' %>
 <%= f.text_field :content %>
 <%= f.submit '投稿' %>
<% end %>
<%= link_to '一覧に戻る', messages_path %>

show から edit へのリンクを追加する
では、 URL の直入力以外でも edit ビューにアクセスできるように詳細ページにリンクを置きましょう。

app/views/messages/show.html.erb

<h1>id: <%= @message.id %> のメッセージ詳細ページ</h1>
<p><%= @message.content %></p>
<%= link_to '一覧に戻る', messages_path %>
<%= link_to 'このメッセージを編集する', edit_message_path(@message) %>

8.9 messages#update
app/controllers/messages_controller.rb の update アクション

  def update
   @message = Message.find(params[:id])
   if @message.update(message_params)
     flash[:success] = 'Message は正常に更新されました'
     redirect_to @message
   else
     flash.now[:danger] = 'Message は更新されませんでした'
     render :edit
   end
 end

View
リダイレクトしているので、 View は不要です。
更新に成功すれば、show.html.erb が表示され、更新に失敗すれば、edit.html.erb が表示されます。

・messages#destroy
app/controllers/messages_controller.rb の destroy アクション

  def destroy
   @message = Message.find(params[:id])
   @message.destroy
   flash[:success] = 'Message は正常に削除されました'
   redirect_to messages_url
 end

ここで、リダイレクトするときに messages_url が出てきました。今まで prefix_path でしたが、リダイレクトの場合だけは prefix_url とすると決まっています。messages_url は Prefix が messages なので index へリダイレクトされます。リダイレクトのときだけ _url を使用しましょう。あまり深く考える必要はありません。

View
リダイレクトしているので View は不要です。
ただし、削除するためのリンクは設置する必要があるので、 show.html.erb に削除リンクを設置します。

app/views/messages/show.html.erb

<h1>id: <%= @message.id %> のメッセージ詳細ページ</h1>
<p><%= @message.content %></p>
<%= link_to '一覧に戻る', messages_path %>
<%= link_to 'このメッセージを編集する', edit_message_path(@message) %>
<%= link_to 'このメッセージを削除する', @message, method: :delete, data: { confirm: '本当に削除してよろしいですか?' } %>

method: :delete が新しいですが、DELETE メソッドを送信することを明示しているだけです。

これで Rails の基本は完了です。Model => Router => Controller + View という流れで開発をしてきました。また、RESTful なルーティングのおかげで、Message の CRUD 操作は完璧に行えます。

8.11 Git

$ git status
$ git diff
$ git add .
$ git commit -m 'CRUD messages'










この記事が気に入ったらサポートをしてみませんか?