【发布时间】:2017-02-02 14:38:36
【问题描述】:
我有两个具有多对一关系的实体。用户有许多地址。创建用户时,我希望表单也创建一个地址。实体是嵌套的。
方法一: 下面的代码有效,但只保存了用户,没有关联的地址。
看了一圈,我认为accepts_nested_attributes_for 会自动保存地址。我不确定,但这可能是因为我进入控制器的参数实际上并没有嵌套,即这不起作用。它们看起来像:
"user"=>{"name"=>"test"}, "address"=>{"address"=>"test"}
而不是像这样嵌套:
"user"=>{"name"=>"test", "address"=>{"address"=>"test"} }
我认为这可能是由于我的表单有问题,但我不知道问题是什么......
方法二:
我也尝试过更改控制器——实现第二个私有方法address_params,它看起来像params.require(:address).permit(:address),然后在create 方法中使用@user.address.build(address_params) 显式创建地址。
当使用调试器跟踪此方法时,地址实体确实已成功创建,但 respond_to do 引发了 ArgumentError,原因我不明白(“respond_to 采用类型或块,从不采用两者”),这会在点击保存方法之前回滚所有内容...
[编辑] - respond_to do 引发错误是一个红鲱鱼 - 我误解了调试器。但是,由于我不明白的原因,事务被回滚了。
问题:
- 对于 Rails,一种或另一种方法更标准吗? (或者也许两者都不是,我从根本上误解了一些东西)
- 我在这两种方法中的任何一种/两种方法中做错了什么,以及如何修复它们以保存用户和地址?
下面的相关代码(它实现了上面的方法 1,并按说明生成非嵌套参数):
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 %>
更新 1:
在进行@Ren 建议的更改后,我可以看到参数看起来更像我对嵌套资源的预期:
"user"=>{"name"=>"test", "addresses_attributes"=>{"0"=>{"address"=>"test"}}}
但是,在尝试保存用户时,由于我不明白的原因,事务仍然回滚。我从 users.new 页面得到的输出是:
2 错误禁止保存此用户:
地址用户必须存在
地址用户不能为空
但是,使用 byebug,在@user = User.new(user_params) 调用之后,事情看起来和我预期的一样:
(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>]>
显然 user.id 字段在记录写入数据库之前没有设置,所以 address.user_id 字段在保存用户之前不能设置,所以这可能是由于 ActiveRecord 时的某种错误排序引起的保存到数据库?我将继续尝试通过使用 byebug 进行调试来了解发生了什么......
更新 2:
使用rails console 进行测试,首先保存用户,然后添加地址工作(两条记录都写入数据库,尽管显然是在两个单独的事务中):
> user = User.new(name: "consoleTest")
> user.save
> user.addresses.build(address: "consoleTest")
> user.save
最后只保存一次会导致我在运行程序时看到的相同问题,即。事务由于某种原因被回滚:
> user = User.new(name: "consoleTest")
> user.addresses.build(address: "consoleTest")
> user.save
据我通过使用rails console 进行调试可以看出,这两种方法中user.addresses 的状态之间的唯一区别是第一个address.user_id 已经设置,因为user.id 是已知的,而在第二个中,它不是。所以这可能是问题所在,但据我了解,save 方法应确保实体以正确的顺序保存,这样这不是问题。理想情况下,能够看到哪些实体 save 正在尝试写入数据库以及以何种顺序写入数据库会很好,但是使用 byebug 进行调试会让我完全不理解 ActiveRecord rabbit-hold!
【问题讨论】:
标签: ruby-on-rails ruby activerecord ruby-on-rails-5 nested-resources