Skip to content

Commit

Permalink
update guides
Browse files Browse the repository at this point in the history
  • Loading branch information
martinemde committed Aug 10, 2024
1 parent b0d378a commit 6c206f6
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def teardown
end
end

test "logs on unexpected nested params with require" do
test "logs on unexpected nested params with expect" do
request_params = { book: { pages: 65, title: "Green Cats and where to find then.", author: "G. A. Dog" } }
context = { "action" => "my_action", "controller" => "my_controller" }
params = ActionController::Parameters.new(request_params, context)
Expand Down
94 changes: 72 additions & 22 deletions guides/source/action_controller_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,49 +315,99 @@ but be careful because this opens the door to arbitrary input. In this
case, `permit` ensures values in the returned structure are permitted
scalars and filters out anything else.

To permit an entire hash of parameters, the [`permit!`][] method can be
used:
[`expect`][] provides a concise and safe way to require and permit parameters.

```ruby
params.require(:log_entry).permit!
id = params.expect(:id)
```

`expect` ensures that the type returned is not vulnerable to param tampering.
The above expect will always return a scalar value and not an array or hash.
When expecting params from a form, use `expect` to ensure that the root key
is present and the attributes are permitted.

```ruby
user_params = params.expect(user: [:username, :password])
user_params.has_key?(:username) # => true
```

`expect` will raise the an error and return a 400 Bad Request response
when the user key is not a nested hash with the expected keys.

When a param is optional but needs to be permitted if it's present, use
[`allow`][] to do both in a single step:

```ruby
new_user = params.allow(user: [:name, :email])
# => {} if params[:user] is not a hash of parameters.
```

To require and permit an entire hash of parameters, [`expect`][] can be
used in this way (substitute `allow` if the params are optional):

```ruby
params.expect(log_entry: {})
```

This marks the `:log_entry` parameters hash and any sub-hash of it as
permitted and does not check for permitted scalars, anything is accepted.
Extreme care should be taken when using `permit!`, as it will allow all current
and future model attributes to be mass-assigned.
Extreme care should be taken when using [`permit!`][] or calling `expect`
with an empty hash, as it will allow all current and future model
attributes to be mass-assigned with external user-controlled params.

[`permit`]: https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-permit
[`permit!`]: https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-permit-21
[`expect`]: https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-expect
[`allow`]: https://api.rubyonrails.org/classes/ActionController/Parameters.html#method-i-allow

#### Nested Parameters

You can also use `permit` on nested parameters, like:
You can also use `expect` (or `permit` or `allow`) on nested parameters, like:

```ruby
params.permit(:name, { emails: [] },
friends: [ :name,
{ family: [ :name ], hobbies: [] }])
# Given the example expected params:
params = ActionController::Parameters.new(
name: "Martin",
emails: ["[email protected]"],
friends: [
{ name: "André", family: { name: "RubyGems" }, hobbies: ["keyboards", "card games"] },
{ name: "Kewe", family: { name: "Baroness" }, hobbies: ["video games"] },
]
)
# the following expect will ensure the params are permitted
name, emails, friends = params.expect(
:name, # permitted scalar
emails: [], # array of permitted scalars
friends: [[ # array of permitted Parameter hashes
:name, # permitted scalar
{
family: [:name], # family: { name: "permitted scalar" }
hobbies: [] # array of permitted scalars
}
]]
)

```

This declaration permits the `name`, `emails`, and `friends`
attributes. It is expected that `emails` will be an array of permitted
scalar values, and that `friends` will be an array of resources with
specific attributes: they should have a `name` attribute (any
permitted scalar values allowed), a `hobbies` attribute as an array of
permitted scalar values, and a `family` attribute which is restricted
to having a `name` (any permitted scalar values allowed here, too).
This declaration permits the `name`, `emails`, and `friends` attributes and
returns them each. It is expected that `emails` will be an array of permitted
scalar values, and that `friends` will be an array of resources (note the new
double array syntax to explicitly require an array) with specific
attributes: they should have a `name` attribute (any permitted scalar values
allowed), a `hobbies` attribute as an array of permitted scalar values, and a
`family` attribute which is restricted to a hash with only a `name` key and
any permitted scalar value.

#### More Examples

You may want to also use the permitted attributes in your `new`
action. This raises the problem that you can't use [`require`][] on the
root key because, normally, it does not exist when calling `new`:
root key because, normally, it does not exist when calling `new`.
Using `allow` for optional parameters permits them when present, but
ignores them, returning an empty hash, when they are absent.

```ruby
# using `fetch` you can supply a default and use
# the Strong Parameters API from there.
params.fetch(:blog, {}).permit(:title, :author)
params.allow(blog: [:title, :author])
```

The model class method `accepts_nested_attributes_for` allows you to
Expand All @@ -366,7 +416,7 @@ parameters:

```ruby
# permit :id and :_destroy
params.expect(author: [:name, { books_attributes: [:title, :id, :_destroy] }])
params.expect(author: [:name, { books_attributes: [[:title, :id, :_destroy]] }])
```

Hashes with integer keys are treated differently, and you can declare
Expand All @@ -380,7 +430,7 @@ with a `has_many` association:
# "chapters_attributes" => { "1" => {"title" => "First Chapter"},
# "2" => {"title" => "Second Chapter"}}}}

params.expect(book: [:title, { chapters_attributes: [:title] }])
params.expect(book: [:title, { chapters_attributes: [[:title]] }])
```

Imagine a scenario where you have parameters representing a product
Expand Down
2 changes: 1 addition & 1 deletion guides/source/form_helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,7 @@ end

private
def person_params
params.expect(person: [:name, { addresses_attributes: [:id, :kind, :street] }])
params.expect(person: [:name, { addresses_attributes: [[:id, :kind, :street]] }])
end
```

Expand Down

0 comments on commit 6c206f6

Please sign in to comment.