RSpec3とFactoryGirlを使ってControllerのテストを作っている時にStrongParametersで苦戦を強いられたのでメモ。
先にまとめると
- FactoryGirlのattributes_for()でフォーム入力データを晴々するときは、本当にフォームから渡すキー以外は削れ
- StrongParametersは入れ子のハッシュデータに対して同階層での網羅性をチェックするので無駄なpermitを行うな
環境
- Ruby on Rails 4.1.8
- RSpec 3.1.7
- FactoryGirl 4.4.0
現象
コントローラーのテストで POST #create のテストを書いていたとき、ActionController::UnpermittedParametersの例外が飛んでテストが成功しない。
前提&想定
Hogeクラスの属性は:id(Integer)、:fuga_id(Integer)、 :title(String)、:text(String)とする。fuga_idは外部キーなので入力フォームからの変更を行わない。
HogesControllerの#create
def create # first_idとsecond_idを使って何かする。 nanka_function(params[:first_id]) @fuga = Fuga.find_by(:id params[:second_id]) @hoge = Hoge.new(hoge_params) @hoge.fuga_id = @fuga.id @hoge.save redirect_to(hoges_path) end def hoge_params params.require(:hoge).permit(:title, :text) end
上記のテストコード
it "新しいHogeオブジェクトが作成される。" do hoge_params = attributes_for(:hoge) expect{ post :create, first_id: @first_id, second_id: @second_id, hoge: hoge_params }.to change(Hoge, :count).by(1) end
期待している入力フォームからのデータ
{ first_id: "1", second_id: "2", hoge: { title: "New Title", text: "New Text."}}
StrongParametersに関するエラーの見逃しを防ぐため例外が飛ぶようにしている. config/environments/test.rbで以下を設定。
config.action_controller.action_on_unpermitted_parameters = :raise
原因その1:FactoryGirl#attributes_for()で作ったハッシュの中に想定外のキーが含まれている
spec/factories/hoges.rb にて以下のように定義していた。
FactoryGirl.define do factory :hoge do fuga_id 2 title "New Title" text "New Text." end end
その結果、attributes_for(:hoge) で生成されるハッシュが以下のようになり、想定と違うものになってしまった。
{fuga_id: "2", title: "New Title", text: "New Text."}
このため、#createに渡されるパラメーターは以下のように「fuga_id: "2"」が余分に付け加えられる。
{ first_id: "1", second_id: "2", hoge: {fuga_id: "2", title: "New Title", text: "New Text."}}
その結果「found unpermitted parameters fuga_id」とエラーがでて、例外も飛ぶ。
入力フォームで渡す予定のない要素は削っておかないといけない。テストコードを以下のように変える。
it "新しいHogeオブジェクトが作成される。" do hoge_params = attributes_for(:hoge) hoge_params.delete(:fuga_id) ## この行を追加 expect{ post :create, first_id: @first_id, second_id: @second_id, hoge: hoge_params }.to change(Hoge, :count).by(1) end
原因その2:中途半端にpermitする
試行錯誤しているうちに以下のように#createを以下のように変更してしまう。
def create nanka_function(params.permit(:first_id)) ## :first_idだけpermitしてしまう @fuga = Fuga.find_by(:id params[:second_id]) @hoge = Hoge.new(hoge_params) @hoge.fuga_id = @fuga.id @hoge.save redirect_to(hoges_path) end
その結果、原因1が解決しているのに「found unpermitted parameters second_id, hoge」のエラーがでる。StrongParametersはハッシュの入れ子の度合いで階層ごとに許可の有無をみている様子。:first_idがpermitの対象になったので、以下のハッシュでいう同階層の:second_idと:hogeがチェックの対象になって上記のエラーがでた様子。
{ first_id: "1", second_id: "2", hoge: {title: "New Title", text: "New Text."}}
素直に「params.permit(:first_id)」を「params[:first_id]」に戻せば問題解決。
おわりに
相当ハマった。