Kintarou'sBlog

プログラミング学習中。学習内容のアウトプットや読書で学んだことなど随時投稿!

【Rails】form_withの送信先

こんにちは😊Kintarouです。

現在エンジニア転職を目指してTECH CAMPにてプログラミング学習中です👨‍🎓
夢はフリーランスエンジニアになって働く人が働きやすいシステムを作ること!
と、愛する妻と海外移住すること🗽

プログラミングや読んでいる本のことなど、ブログに書いていきます!
twitter : https://twitter.com/ryosuke_angry


今日は最終課題のフリマアプリに追加実装をしていたのですが、そこで起きた問題と解決までの道のりを書きます!

【問題】保存できていたはずの画像データが保存できなくなった!

状況ー複数の画像を保存する機能の後、タグ付け機能を実装するとエラーは発生しないのに画像の保存がされず、TOPページなどに画像が表示されない。

データの流れの想定ー
①form_withにて画像データが送信され、items_controllerのcreateアクションが実行される。
②item_paramsにはimages: [](複数の画像を保存するため配列として受け取る)として取得され、それをもとにItemTagのインスタンスを生成。(タグ機能実装時にフォームオブジェクトを作成しているため)
③フォームオブジェクトに定義されたsaveメソッドにより、各テーブルに保存。

今回問題は②で起きましたが、原因は①にありました...
まずは検証から

【検証内容】どの段階で画像データが途切れているのかを検証

検証①ーcreateアクションにbinding.pryを仕掛け、パラメーターが送られているのか検証。
結果①ー画像の保存場所(images)にはデータが入っている。

[1] pry(#<ItemsController>)> params  
=> <ActionController::Parameters   {"authenticity_token"=>"YoGa54VxNfeDLKgKZGK5pM8zKZ665QkECZl51BvmpK6FqPDnMr7IKKB7lVn4gKKgl0I4jhaqjdsomeJ1+60tTQ==",  
"item"=>{"images"=>[#<ActionDispatch::Http::UploadedFile:0x00007f97bc1dedd0  
@tempfile=#<Tempfile:/var/folders/93/yxd05g657jv1qbxv2bj70gf00000gn/T/RackMultipart20200918-1389-17p8adf.jpeg>,  
@original_filename="1ADA53AF-DEAD-417D-A444-62913EB2C5FF.jpeg",   @content_type="image/jpeg",  
@headers="Content-Disposition: form-data; name=\"item[images][]\";  
filename=\"1ADA53AF-DEAD-417D-A444-62913EB2C5FF.jpeg\"\r\nContent-Type: image/jpeg\r\n">]},  
"item_tag"=>{"name"=>"test", "message"=>"test", "category_id"=>"1", "sales_status_id"=>"1", "shipping_fee_id"=>"1", "prefecture_id"=>"1", "scheduled_delively_id"=>"1", "tag_name"=>"12345", "price"=>"300000"},  
"commit"=>"出品する", "controller"=>"items", "action"=>"create"} permitted: false>

imagesに@original_filename="1ADA53AF-DEAD-417D-A444-62913EB2C5FF.jpeg"が入ってたので問題ないと思ってたのですが、このparamsの送信先に問題があったようです...
後ほど解説します。

検証②ーcreateアクション内、@item = ItemTag.new(item_params)の後にbinding.pryを仕掛け、@itemのimagesに保存されているか検証。
結果②ーimagesにデータが入っていない。

[2] pry(#<ItemsController>)> @item
=> #<ItemTag:0x00007fe33ec12ba8
 @category_id="3",
 @images=[],
 @item_id=nil,
 @message="fugafuga",
 @name="hogehoge",
 @prefecture_id="4",
 @price="12345",
 @sales_status_id="2",
 @scheduled_delively_id="2",
 @shipping_fee_id="2",
 @sold=false,
 @tag_id=nil,
 @tag_name="foobar",
 @user_id=4>

imagesが となっていて空の配列となっています...(配列としてのオブジェクトはあるためバリデーションにも引っかからずエラーにはなりません)

【検証結果】パラメーターとして送られてはいるが、レコードを作る段階でimagesの配列に格納されていない。

私の力がここまでで、あとはメンターさんに相談させて頂きました。
お二人相談させて頂きましたがどちらも博識でかつ優しい方でした。

【原因】imagesだけitem_tagのキーとして送られていない!

検証①の結果のログですが、nameやmessageなどのキーはitem_tagとして送られているのですが、imagesだけ何故かitemのキーとして送られているんですね。
これでは「”item"の画像データはこれだよ!ほい!」と来られても「いやいや、images: の受け皿は "item_tag" 宛に置いてるよ!」となるので受け取れないわけです。

【解決策①】itemのimages: [] として受け皿を作る

送る先が分かればそこに受け皿を置けばいい話です。
item_paramsの内容を変更してみます。

def item_params
    params.require(:item_tag).permit(
      :name,  
      :message,  
      :price,  
      :category_id,  
      :sales_status_id,  
      :shipping_fee_id,  
      :prefecture_id,  
      :scheduled_delively_id,  
      :tag_name,  
      #images: [] これをmarge部分に移動
    )  
    .merge(  
      user_id: current_user.id,  
      item_id: params[:item_id],  
      tag_id: params[:tag_id],  
      images: [] #ここに追加
    )  
end

でOKかと思いきやこれでは不足だそうです。
params.require(:item).permit(images:[])をmergeに入れる場合

images: params[:item][:images]

とする必要があるそうなので、正確には↓です。

def item_params
    params.require(:item_tag).permit(
      :name,  
      :message,  
      :price,  
      :category_id,  
      :sales_status_id,  
      :shipping_fee_id,  
      :prefecture_id,  
      :scheduled_delively_id,  
      :tag_name  
    )  
    .merge(  
      user_id: current_user.id,  
      item_id: params[:item_id],  
      tag_id: params[:tag_id],  
      images: params[:item][:images]  #修正
    )  
end

これでもいいそうなのですが、別解があるのでそちらも書いておきます。

【解決策②】そもそものフォームの送信先を統一する

こちらの方が正答のようです。
確かにフォームの送り先をitem_tagに統一すれば問題ないですね。

<%= form_with model:@item, url:items_path, local: true do |f| %>  
#newアクションで @item = ItemTag.new としている。

#省略  

#<%= f.file_field :images, name: 'item[images][]', id:"item-image" %>  
<%= f.file_field :images, name: 'item_tag[images][]', id:"item-image" %>  

#省略

こうすることでフォームの送信先をitem_tagに統一出来ました。

【まとめ】学んだこと

params.require(:hoge).permit(:fuga)は params[:hoge][:fuga] ということ。

form_withの送信先を指定する方法は2種類。
①model: に指定
②name属性で指定

ということで、真の原因は私が画像の複数保存時に設定したname属性 ( item[images][] ) をフォームオブジェクト作成時に修正していなかったことでした笑

長々話しましたが、色々学べて良かったです😊
また、どなたかの参考になれば幸いです。