DEV Community

Barry Hess
Barry Hess

Posted on • Originally published at goodenough.us

Rails has_one Nested Attributes Tweaking

In a project I'm working on right now I've been using a Rails nested form 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:

<%= 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 %>
Enter fullscreen mode Exit fullscreen mode

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

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"} }
Enter fullscreen mode Exit fullscreen mode

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 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:

<%= form.fields_for :customization, include_id: false do |customization_form| %>
Enter fullscreen mode Exit fullscreen mode

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:

Customization Destroy (0.4ms)  DELETE FROM "customizations" WHERE "customizations"."id" = $1  [["id", 10]]
Enter fullscreen mode Exit fullscreen mode

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 post linking to some documentation for 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:

accepts_nested_attributes_for :customization, update_only: true
Enter fullscreen mode Exit fullscreen mode

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.

def user_params
  params.require(:user).permit(
    :name, :email,
    customization_attributes: [
      :id, :primary_color, :background_color
    ]
  )
end
Enter fullscreen mode Exit fullscreen mode

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

This post originally appeared at the Good Enough blog.

Top comments (0)