Rails has_one Nested Attributes Tweaking
Tags: rails, ruby
Published: 2023-08-10 16:11:58

In a project I'm working on right now I've been using a [Rails nested form](https://guides.rubyonrails.org/form_helpers.html#nested-forms) and a couple of things caught me off guard.

### `has_one` Nested Form Sending `id` Attribute

In this case I have a nested form that is in a `has_one` relationship with the parent model.
I think this is a common thing to do, especially if you want to offload less-frequently-accessed data to an auxiliary table in your database.
For this example we have a `User` that can define some customizations in the product.
A `User has_one Customization` as it were.

On a settings page I would like the `User` to be able to update some `User` things as well as some `Customization` things.
Thus there is a nested set of form fields:

```html
<%= form.fields_for :customization do |customization_form| %>
  <%= customization_form.label :primary_color do %>
    <%= customization_form.color_field :primary_color %>
    Primary color
  <% end %>
  <%= customization_form.label :background_color do %>
    <%= customization_form.color_field :background_color %>
    Background color
  <% end %>
<% end %>
```

This works well, save for one thing that bothered.
In my Rails logs I saw (edited for readability):

```ruby
Unpermitted parameter: :id. Context: { controller: UsersController, action: update, request: #<ActionDispatch::Request:0x000000010b4832b0>, 
params: {"_method"=>"patch", "authenticity_token"=>"[FILTERED]", 
  "user"=>{"name"=>"Barry Hess", 
    "customization_attributes"=>{
      "primary_color"=>"#363b5c", "background_color"=>"#fdffb2", "id"=>"10"}}, 
  "button"=>"", "controller"=>"users", "action"=>"update", "id"=>"3"} }
```

It's not entirely clear from that message, but the problem is the `customization_attributes: "id"` parameter.
In this case I don't need an `id` to find my `has_one` `Customization` record.
There us one and only one!

At the bottom of the [Rails Guides documentation for nested forms](https://guides.rubyonrails.org/form_helpers.html#nested-forms) I found this lovely sentence:

> If the associated object is already saved, `fields_for` autogenerates a hidden input with the `id` of the saved record. 
> You can disable this by passing `include_id: false` to `fields_for`.

And thus begat the new intro to the nested form:

```html
<%= form.fields_for :customization, include_id: false do |customization_form| %>
```

Yay!

### `has_one` Deleting Its Record and Recreating It Upon Save

After resolving that issue I was struck with another surprising set of log messages.
Some smart past me must have figured out how to get colorization in my logs because in bright red I was seeing that this simple form update was deleting my Customization record:

```ruby
Customization Destroy (0.4ms)  DELETE FROM "customizations" WHERE "customizations"."id" = $1  [["id", 10]]
```

This was followed by an assocationed `INSERT INTO "customizations"`.
I didn't really feel like this was representing what was happing in the application at all!
So I googled "rails has_one delete and recreate table" and I found a handy [Stack Overflow](https://stackoverflow.com/a/25212918/14895) post linking to [some documentation for `accepts_nested_attributes_for`](http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for):

> **`:update_only`**  
> By default the `:update_only` option is `false` and the nested attributes are used to update the existing record only if they include the record's `:id` value. Otherwise a new record will be instantiated and used to replace the existing one.

On my `User` model I chose to go with:

```ruby
accepts_nested_attributes_for :customization, update_only: true
```

### There Is a Way with Less Code, but Is It Better?

Both of these items could have been solved if I simply allowed `customizations#id` as a permitted parameter at the controller level.

```ruby
def user_params
  params.require(:user).permit(
    :name, :email,
    customization_attributes: [
      :id, :primary_color, :background_color
    ]
  )
end
```

I just…don't feel great about that, though I'm not sure why.
Certainly I would write some comments with `params` permissions including an `id`.
Those comments would probably linking to this blog post.

---

Curious for more? [Please subscribe to A Good Enough Newsletter](https://goodenoughnews.substack.com/)

_This post [originally appeared at the Good Enough blog](https://goodenough.us/blog/2023-08-07-til-rails-has-one-nested-attributes-tweaking/)._