0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rails: controller層でパラメータをrequireに設定すると空配列が弾かれてしまう

Last updated at Posted at 2024-05-15

奮闘したこと

本記事では、Railsで作成したAPIのリクエストボディに配列をトップレベルで持った時、空配列でputリクエストを送れなくなり、詰まったのでそれを解説します。
(どうしてもトップレベルで持ちたい場合、#追記欄に最終的な着地点を記載しています。)

Railsのrequireメソッドは、特定のパラメータがリクエストに含まれていることを確認するために使用されます。もし指定されたキーが存在しない、または値が空である場合(例えば空の配列[])、RailsはActionController::ParameterMissingエラーを発生させます。これは、requireがリクエストパラメータの存在とその値の有効性を保証するために設計されているためです。

空配列がnil扱いされる問題

リクエストボディのトップレベルに配列を用いると意図しない挙動が発生することがあります。具体的には、配列がnil扱いさてしまうのです。これが原因でエラーが発生します。以下は、具体例を用いて説明します。

例: リクエストボディのトップレベルに配列を使用する場合

リクエストボディは以下のような形です。

{
  "tag_ids": [1, 2, 3]
}

そしてrailsのcontroller層は以下。

# 存在かつ空でないことをrequire
params.require(:tags)

このコードでは、:tagsキーが存在し、その値が空でないことを要求しています。しかし、リクエストボディに空の配列を含めると、Railsはその配列を無視し、ActionController::ParameterMissingエラーを発生させます。

params.permit(tags: [])

空配列を許可する必要がある場合は、permitメソッドを使用して明示的に許可する必要があります。
しかし、これは以下Railsガイドにもあるように問題もあります。そのため、まとめでお話しするリクエストボディの変更が最善案ではないかと思います。

「paramsの値には許可されたスカラー値の配列を使わなければならない」ことを宣言するには、以下のようにキーに空配列を対応付けます。
params.permit(id: [])
ハッシュパラメータやその内部構造の正しいキーをすべて明示的に宣言できない場合や、すべて宣言するのが面倒な場合があります。次のように空のハッシュを割り当てることは一応可能です。
params.permit(preferences: {})
ただし、この指定は任意の入力を受け付けてしまうため、利用には十分ご注意ください。この場合permitによって、受け取った構造内の値が許可済みのスカラーとして扱われ、それ以外の値がフィルタで除外されます。

RSpecでの問題: 空文字が勝手に格納される

RSpecでテストを行う際、空の配列をリクエストボディに含めると、Railsがこの配列を空文字に変換してしまうことがあります。これにより、テストが失敗する原因となります。

例: RSpecでのテスト

let!(:params) do
  {
    tag_ids: []
  }
end

この例では、tag_idsキーに空の配列を指定していますが、RSpecではこれが空文字に変換される可能性があります。

Received parameters: #<ActionController::Parameters {"tag_ids"=>[""], "format"=>"json", "controller"=>"api/v1/articles", "action"=>"update"} permitted: false>

このように、tag_ids: [][""]に変換されるため、requireを突破してしまいます。

context 'tag_ids=[]が指定されている場合' do
  let!(:article) { create(:article) }
  let!(:params) do
    {
      tag_ids: []
    }.to_json
  end

まとめ: 正しいリクエストボディの形式

正しいリクエストボディの形式を使用することで、空配列が正しく処理されることを確認できます。
requireはオブジェクトに対してを設定しようということかもですね。

{
  "article": {
    "tag_ids": [1, 2, 3]
  }
}

ただ、params.require(:article)としても以下の形はbad requestにならないため気が必要かと思われる。

{
  article: {
    "tag_ids": nil
  }
}

RSpecでの解決策

RSpecで空文字変換を避けるには、以下のようにします。

let!(:params) do
  { article: { tag_ids: [] }}.to_json
end

この方法により、空配列が正しく受け取られることを確認できます。RSpecでのテストコードも含めて、適切なパラメータを使用することで、テストの信頼性を向上させることができます。

追記(2024/10/28)

上記にて、議論させていただいた結果、permitfetch を組み合わせるとトップレベルのキーもうまく扱えることがわかりました。

irb(main):001> ActionController::Parameters.new({ foo: [1] }).permit(foo: []).fetch(:foo)
=> [1]
irb(main):002> ActionController::Parameters.new({ foo: [] }).permit(foo: []).fetch(:foo)
=> []
irb(main):003> ActionController::Parameters.new({ foo: nil }).permit(foo: []).fetch(:foo)
(irb):3:in `<main>': param is missing or the value is empty: foo (ActionController::ParameterMissing)
irb(main):004> ActionController::Parameters.new({}).permit(foo: []).fetch(:foo)
(irb):4:in `<main>': param is missing or the value is empty: foo (ActionController::ParameterMissing)

fetch(:foo) を使用して、キーが存在しない場合や nil である場合には、ActionController::ParameterMissing エラーを発生させる感じですね。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?