I have two entities with a many-to-one relationship. User has many Addresses. When creating a User I want the form to also create a single Address. The entities are nested.
Approach 1: The code below works, but only saves the User, no associated Address.
Reading around, I thought that the accepts_nested_attributes_for
would automatically save the address. I'm not sure, but it may be that this isn't working because the parameters I'm getting into the Controller don't actually appear to be nested, ie. they look like:
"user"=>{"name"=>"test"}, "address"=>{"address"=>"test"}
Rather than being nested like this:
"user"=>{"name"=>"test", "address"=>{"address"=>"test"} }
I assume this could be due to something wrong in my form, but I don't know what the problem is...
Approach 2:
I have also tried changing the controller - implementing a second private method, address_params
, which looked like params.require(:address).permit(:address)
, and then explicitly creating the address with @user.address.build(address_params)
in the create
method.
When tracing through this approach with a debugger the Address entity did indeed get created successfully, however the respond_to do
raised an ArgumentError for reasons I don't understand ("respond_to takes either types or a block, never both"), and this rolls everything back before hitting the save method...
[EDIT] - The respond_to do
raising an error was a red herring - I was misinterpreting the debugger. However, the transaction is rolled back for reasons I don't understand.
Questions:
- Is one or the other approach more standard for Rails? (or maybe neither are and I'm fundamentally misunderstanding something)
- What am I doing wrong in either / both of these approaches, and how to fix them so both User and Address are saved?
Relevant code below (which implements Approach 1 above, and generates the non-nested params as noted):
user.rb
class User < ApplicationRecord
has_many :address
accepts_nested_attributes_for :address
end
address.rb
class Address < ApplicationRecord
belongs_to :user
end
users_controller.rb
class UsersController < ApplicationController
# GET /users/new
def new
@user = User.new
end
# POST /users
# POST /users.json
def create
@user = User.new(user_params)
respond_to do |format|
if @user.save
format.html { redirect_to @user, notice: 'User was successfully created.' }
format.json { render :show, status: :created, location: @user}
else
format.html { render :new }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
private
def user_params
params.require(:user).permit(:name, address_attributes: [:address])
end
end
_form.html.erb
<%= form_for(user) do |f| %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<%= fields_for(user.address.build) do |u| %>
<div class="field">
<%= u.label :address %>
<%= u.text_field :address %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
UPDATE 1:
After making the changes suggested by @Ren, I can see that the parameters look more like what I would've expected for nested resources:
"user"=>{"name"=>"test", "addresses_attributes"=>{"0"=>{"address"=>"test"}}}
However, when trying to save the user, the transaction is still rolled back for reasons I don't understand. The output I get from the users.new page is:
2 error prohibited this user from being saved:
Addresses user must exist
Addresses user can't be blank
However, using byebug, after the @user = User.new(user_params)
call, things look as I would expect them:
(byebug) @user
#<User id: nil, name: "test", created_at: nil, updated_at: nil>
(byebug) @user.addresses
#<ActiveRecord::Associations::CollectionProxy [#<Address id: nil, user_id: nil, address: "test", created_at: nil, updated_at: nil>]>
Obviously the user.id field is not set until the record is written to the DB, so equally the address.user_id field cannot be set until user is saved, so maybe this is caused by some sort of incorrect ordering when ActiveRecord is saving to the database? I will continue to try to understand what's going on by debugging with byebug...
UPDATE 2:
Using rails console
to test, saving User first and then adding the Address works (both records get written to the DB, although obviously in 2 separate transactions):
> user = User.new(name: "consoleTest")
> user.save
> user.addresses.build(address: "consoleTest")
> user.save
Saving only once at the end results in the same issues I'm seeing when running my program, ie. the transaction is rolled back for some reason:
> user = User.new(name: "consoleTest")
> user.addresses.build(address: "consoleTest")
> user.save
As far as I can tell from debugging with rails console
, the only difference between the state of user.addresses
in these two approaches is that in the first address.user_id
is already set, since the user.id
is already known, while as in the second, it is not. So this may be the problem, but from what I understand, the save
method should ensure entities are saved in the correct order such that this is not a problem. Ideally it would be nice to be able to see which entities save
is trying to write to the DB and in which order, but debugging this with byebug takes me down an ActiveRecord rabbit-hold I don't understand at all!