Railsでコントローラに渡すパラメータの順番を意識しようと思った話

## はじめに
この記事は[ソニックガーデン 若手プログラマ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/sonicgarden-junior) の20日目の記事です。
今回のアドベントカレンダーでは2回目の投稿です！💪([1回目の記事](https://qiita.com/hattsuan/items/f66132584b5a62771c8c))

今回はRailsを使ってダイレクトアップロード機能を実装したのですが、コントローラが受け取るフォームのパラメーターの順番が原因で少しハマったのでご紹介。

## 開発環境
- Ruby 3.3.6
- Rails 8.0.0

## 実装

### ダイレクトアップロード機能の実装
[Railsガイド](https://railsguides.jp/active_storage_overview.html#%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89)を参考にしながら、実装しました。
どういう問題が起きるか予想しながら読んでみると面白いかもです。

```app/views/admin/books/_form.html.haml
= simple_form_for [:admin, book] do |f|
  = f.input :front_cover, input_html: { direct_upload: true }
  = f.input :front_cover, as: :hidden, input_html: { value: book.front_cover.signed_id } if book.front_cover.attached?
  = image_tag book.front_cover.variant(:thumbnail), class: 'mb-5' if book.front_cover.attached?
  = f.button :submit
```

※まんまscaffoldです。
```app/controllers/admin/books_controller.rb
class Admin::BooksController < Admin::ApplicationController
  before_action :set_book, only: %i[show edit update]

  def new
    @book = Book.new
  end

  def create
    @book = Book.new(book_params)
    if @book.save
      redirect_to admin_book_path(@book), notice: t('controller.created')
    else
      render :new, status: :unprocessable_entity
    end
  end

  def show
  end

  def edit
  end

  def update
    if @book.update(book_params)
      redirect_to admin_book_path(@book), notice: t('controller.updated'), status: :see_other
    else
      render :edit, status: :unprocessable_entity
    end
  end

  private

  def set_book
    @book = Book.find(params.expect(:id))
  end

  def book_params
    params.expect(book: %i[ front_cover ])
  end
end
```
### 起きたこと
編集フォームで表紙画像の更新ができませんでした。


### 調査
updateアクションの中で`params[:book][:front_cover]`が既に紐づいているbookのfront_coverのsigned_idと同じ値でした。(signed_idをご存知ない方は、[こちらの記事](https://qiita.com/kxhira/items/29505364dda07ac043f7)をご覧いただくと分かり易いかと思います。)

新しいファイルを添付して送信してるはずなのに、なぜ前回の画像のsigned_idと同じなのか。
そういえばフォームのfront_coverパラメータが1つだけど、もしかしてhiddenパラメータの行が優先されているのでは？と思い、試しにフィールドを逆にしてみたところ、ちゃんと画像が更新されるようになりました✨
```diff_ruby:haml
-  = f.input :front_cover, input_html: { direct_upload: true }
-  = f.input :front_cover, as: :hidden, input_html: { value: book.front_cover.signed_id } if book.front_cover.attached?

+  = f.input :front_cover, as: :hidden, input_html: { value: book.front_cover.signed_id } if book.front_cover.attached?
+  = f.input :front_cover, input_html: { direct_upload: true }
```

パラメータが重複していた場合、後ろの値が上書きするみたいですね。
一応コンソールで確認してみました。

```irb
 ActionController::Parameters.new({
     book:
     { name: 'aaa',
       name: 'bbb'
     }
   }
 )
(cherry-online):48: warning: key :name is duplicated and overwritten on line 49
=> #<ActionController::Parameters {"book"=>{"name"=>"bbb"}} permitted: false>
>> 
```
aaaではなく、後ろに存在するbbbの値で上書きしていますね。

実は[Active Storageのサンプルコード](https://railsguides.jp/active_storage_overview.html#%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%81%AE%E3%83%90%E3%83%AA%E3%83%87%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)には、ちゃんと新しくアップロードする画像のフィールドをhiddenパラメータの下に書いてありました。。。
お恥ずかしい。なんのためのサンプルコードやねん。



## 最後に

今までパラメータが重複するということがなかったので、いい学びになりました。
最後まで読んでいただき、ありがとうございました〜！
明日は [@samayou](https://qiita.com/samayou) さんが執筆予定です。お楽しみに〜！