Filter and Require Params in Rails 8 with Parameters#expect

Rails 8 introduces params#expect, a new method that enhances parameter handling by filtering parameters based on expected types. This reduces errors caused by tampering or invalid input.

Before

ActionController parameters allows us to choose which attributes should be permitted with the help of require and permit. By default, the recommended way of handling parameters in Rails works fine. Until someone using our app starts messing with the parameters and causing 500 errors.

params.require(:post).permit(:title, :summary, categories: [:name])
http://localhost:3000/?post[title]=Hello World

#=> {"title"=>"Hello World"}
Passing a String Instead of a Hash

If someone tampered params by passing string instead of hash. This throws NoMethodError because the permit is called on string.

http://localhost:3000/?post=Hello World

#=> {"post"=>"Hello World"}

Completed 500 Internal Server Error in 28ms (ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 23.3ms)

NoMethodError undefined method `permit' for "Hello World":String
Passing an Array to params.require

If an array is passed to params.require(:id), which alter the behaviour and throws 404 error with ActiveRecord::RecordNotFound.

Post.find(params.require(:id))
http://localhost:3000/?id[]=1&id[]=2&id[]=3

Completed 404 Not Found in 33ms (ActiveRecord: 14.2ms (1 query, 0 cached) | GC: 1.5ms)

ActiveRecord::RecordNotFound (Couldn't find all Posts with 'id': (1, 2, 3) (found 1 results, but was looking for 3).):

After

Rails 8 addresses these issues by introducing the params#expect method, which offers more explicit and safer parameter filtering.

New Syntax with params#expect

The params.expect method ensures params are filtered with consideration for the expected types of values, improves handling of params and avoids ignorable errors caused by params tampering.

params.require(:table).permit(:attr)
# Replaces the above one

params.expect(table: [ :attr ])
Tampering with a String Instead of a Hash

Now if someone tampers params by passing string instead of hash, It throws 400 error instead of 500 error with ActionController::ParameterMissing.

params.expect(post: [ :title, :summary, categories: [[:name]] ])
http://localhost:3000/?post=Hello World

#=> {"post"=>"Hello World"}

Completed 400 Bad Request in 15ms (ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 0.0ms)

ActionController::ParameterMissing (param is missing or the value is empty or invalid: post):
New Double Array Syntax for Arrays of Parameter Hashes

We can also notice the new double array [[:name]] in the params.expect for categories.

params.expect(post: [ :title, :summary, categories: [[:name]] ])

The expect method requires us to declare expected arrays in this [[:attr]] way. If we pass normal array [:attr] like categories: [:name], it ignores it.

The permit does not adopt the new double array syntax and is more permissive about unexpected types. Using expect everywhere is recommended.

Replacing params.require(:id)

We can also replace params.require(:id) with params.expect(:id).

The params.expect(:id) is designed to ensure that params[:id] is a scalar and not an array or hash, also requiring the param.

Now if someone passes the array to it, throws 404 error with ActionController::ParameterMissing.

http://localhost:3000/?id[]=1&id[]=2&id[]=3

#=> Completed 400 Bad Request in 25ms (ActiveRecord: 0.0ms (0 queries, 0 cached) | GC: 0.2ms)

#=> ActionController::ParameterMissing (param is missing or the value is empty or invalid: id)
Additional Method: params#expect!

Rails 8 also adds expect! method which raises an unhandled error in cases where expect would raise a handled error that would return a 400 bad request.

Conclusion

The addition of Parameters#expect significantly enhances parameter management by providing a more streamlined, clear, and safe way to handle incoming data.

Need help on your Ruby on Rails or React project?

Join Our Newsletter