Edited at

Rails hidden_field_tagの引数でURIパターンが変わることの気づき

More than 1 year has passed since last update.


概要: URIパターンがhidden_field_tagの引数によって変化することのまとめ


確認:本題に入る前に寄り道。Viewから渡されるデータはルーティングで新たにkey, valueのハッシュを形成する。

ex)


routes.rb

Rails.application.routes.draw do

resources :tweets do
end
end

rake routesの結果↓


rake_routes

        Prefix Verb   URI Pattern                          Controller#Action

tweets GET /tweets(.:format) tweets#index
POST /
tweets(.:format) tweets#create
new_tweet GET /tweets/new(.:format) tweets#new
edit_tweet GET /tweets/:id/edit(.:format) tweets#edit
tweet GET /tweets/:id(.:format) tweets#show
PATCH /tweets/:id(.:format) tweets#update
PUT /tweets/:id(.:format) tweets#update
DELETE /tweets/:id(.:format) tweets#destroy

例えばindexのViewでtweetを削除するボタンがあったとする。


index.html.erb

<%= link_to '削除', "/tweets/#{tweet.id}", method: :delete %>


任意のtweetを削除する場合


1. Viewで削除する内容を指定する。

2. 指定された#{tweet.id}のデータがルーティングに渡される。

3. DELETE /tweets/:id(.:format)で新たに{'id' => 'tweet.id'}というハッシュを形成する。

4. tweetsコントローラのdestroyアクションが動作する。


tweets_controller.rb

    def destroy

tweet = Tweet.find(params[:id])
tweet.destroy
end

5.上記のようにparams[:id]とルーティングで新たに形成されたハッシュのkeyを指定することでViewで指定したtweetを取得出来る。

resourcesを使わない場合、以下のようにpathを設定したとする。( DELETEのみ変更 )


rake_routes

        Prefix Verb   URI Pattern                          Controller#Action

tweet_comments POST /tweets/:tweet_id/comments(.:format) comments#create
tweets GET /tweets(.:format) tweets#index
POST /
tweets(.:format) tweets#create
new_tweet GET /tweets/new(.:format) tweets#new
edit_tweet GET /tweets/:id/edit(.:format) tweets#edit
tweet GET /tweets/:id(.:format) tweets#show
PATCH /tweets/:id(.:format) tweets#update
PUT /tweets/:id(.:format) tweets#update
DELETE /tweets/:xxx(.:format) tweets#destroy

この場合destroy コントローラでviewから渡されるtweetを取得するには、params[:xxx]と書いてあげれば良い。


本題:newアクションへのURIパタンーンってどうやって決まったの??

例えば質問(question)に対して、回答(answer)出来るアプリケーションを想定する。

条件として質問1つにつき1ユーザーは1回限りの回答とする。流れは以下の通り。

ex)


routes.rb

Rails.application.routes.draw do

root to: "top#index"
resources :questions, only: [:create, :show]
resources :answers, only: [:new, :create]
end

rake routesの結果↓


rake_routes

    Prefix Verb URI Pattern              Controller#Action

root GET / top#index
questions POST /questions(.:format) questions#create
question GET /
questions/:id(.:format) questions#show
answers POST /answers(.:format) answers#create
new_answer GET /
answers/new(.:format) answers#new

モデルの関係


answer.rb

class Answer < ActiveRecord::Base

belongs_to :question
end


question.rb

class Question < ActiveRecord::Base

has_many :answers
end

フロントのViewはこんな感じ↓


index.html.erb

<div class="question_content top_content">

<header class="question_header">
<div class="question_image icon_image" style="background-image: url(<%= question.user.avatar.url(:medium) %>);"></div>
<div class="question_user_info user_info">
<span><%= link_to question.user.name, user_path(question.user) %></s
pan>
<time><%= time_ago_in_words question.created_at %></time>
</div>
</header>
<div class=
"question_body">
<div class="question_box">
<span class="q">Q</span>
<span class="question_text"><%= question.text %></s
pan>
</div>
<div class="questioned">
<%= link_to "回答
#{question.answers.count}件", question_path(question) %>
</
div>
<% unless question.answers.exists?(user_id: current_user.id) %>
<%= form_tag(new_answer_path, method: :get, class: "question_submit") do %>
<%=
hidden_field_tag(:question_id, question.id) %>
<%= submit_tag("回答する") %>
<% end %>
<% else %>
<div class=
"answer_other">
<h4>※回答済み※</h4>
</
div>
<% end %>
</div>
</
div>

スクリーンショット 2017-11-11 17.42.18.jpg

回答するボタンを押すと、answersコントローラのnewアクションが動作する。

ここで疑問が生じた。

answers#newのURIパターンである


rake_routes

一部抽出

new_answer GET /answers/new(.:format) answers#new

回答するボタンを試しに押したときのURIがこちら。


URI

http://localhost:3000/answers/new?utf8=✓&question_id=4&commit=回答する


localhost:3000/answers/newのあとに色々ついてるが、これは一体どこから。。。

ここで回答するを押した場合のURIについてviewを操作して確認してみた。

hidden_field_tag(:question_id, question.id)を削除


index.html.erb

<div class="question_content top_content">

<header class="question_header">
<div class="question_image icon_image" style="background-image: url(<%= question.user.avatar.url(:medium) %>);"></div>
<div class="question_user_info user_info">
<span><%= link_to question.user.name, user_path(question.user) %></s
pan>
<time><%= time_ago_in_words question.created_at %></time>
</div>
</header>
<div class=
"question_body">
<div class="question_box">
<span class="q">Q</span>
<span class="question_text"><%= question.text %></s
pan>
</div>
<div class="questioned">
<%= link_to "回答
#{question.answers.count}件", question_path(question) %>
</
div>
<% unless question.answers.exists?(user_id: current_user.id) %>
<%= form_tag(new_answer_path, method: :get, class: "question_submit") do %>
<!-- hidden_field_tag(:question_id, question.id)を削除 -->
<%=
submit_tag("回答する") %>
<% end %>
<% else %>
<div class="answer_other">
<h4>※回答済み※</h4>
</
div>
<% end %>
</div>
</
div>


URI

http://localhost:3000/answers/new?utf8=✓&commit=回答する


おっ!URIの中から &question_id=4 が消えている。

つまりViewでhidden_field_tag(:question_id, question.id)と記述することで、newアクションへのURIパターンの中に

question_id=question.idを渡していたのだ。てなわけで、どのquestion.idに対するAnswerなのかを定義するには以下のように指定すれば良い。


answers_controller.rb

class AnswersController < ApplicationController

def new
@question = Question.find(params[:question_id])
@answer = Answer.new
@answer.question_id = @question.id
end

end


また一度回答したユーザーがURLから直接回答ページを開くことが出来ないように以下のようにcallbackを指定する。


answers_controller.rb

class AnswersController < ApplicationController

before_action :redirect, only: :new

def new
@question = Question.find(params[:question_id])
@answer = Answer.new
@answer.question_id = @question.id
end

def redirect
if Answer.exists?(question_id: params[:question_id], user_id: current_user.id)
redirect_to :root
end
end

end


pry-railsでnewアクションに渡されたparamsの中身を確認した結果がこちら。

スクリーンショット 2017-11-11 18.03.35.jpg

確かにViewで指定したhidden_field_tag(:question_id, question.id)が渡されている。

そのためコントローラで

  @question = Question.find(params[:question_id])

とすることでルーティングからquestion_idをkeyとしたvalueの4が取得出来るのであった。