Jas*_*ase 20 activerecord ruby-on-rails nested-forms nested-attributes
使用Rails 2.3.8
目标是创建一个Blogger,同时更新嵌套的用户模型(如果信息已更改等),或者创建一个全新的用户(如果它尚不存在).
模型:
class Blogger < ActiveRecord::Base
belongs_to :user
accepts_nested_attributes_for :user
end
Run Code Online (Sandbox Code Playgroud)
Blogger控制器:
def new
@blogger = Blogger.new
if user = self.get_user_from_session
@blogger.user = user
else
@blogger.build_user
end
# get_user_from_session returns existing user
# saved in session (if there is one)
end
def create
@blogger = Blogger.new(params[:blogger])
# ...
end
Run Code Online (Sandbox Code Playgroud)
形成:
<% form_for(@blogger) do |blogger_form| %>
<% blogger_form.fields_for :user do |user_form| %>
<%= user_form.label :first_name %>
<%= user_form.text_field :first_name %>
# ... other fields for user
<% end %>
# ... other fields for blogger
<% end %>
Run Code Online (Sandbox Code Playgroud)
当我通过嵌套模型创建新用户时工作正常,但如果嵌套用户已经存在并且具有和ID(在这种情况下我希望它只是更新该用户),则会失败.
错误:
Couldn't find User with ID=7 for Blogger with ID=
Run Code Online (Sandbox Code Playgroud)
这个SO问题涉及类似的问题,只有答案表明Rails根本不会那样工作.答案建议只是传递现有项目的ID而不是显示它的表单 - 这样可以正常工作,除非我想允许编辑User属性(如果有的话).
建议?这似乎不是一个特别罕见的情况,似乎必须有一个解决方案.
Tyl*_*ick 49
我正在使用Rails 3.2.8并遇到完全相同的问题.
看来你正在尝试做什么(将现有的已保存记录分配/更新到新未保存的父模型()的belongs_to
关联(user
)根本不可能在Rails 3.2.8(或Rails 2.3.8,就此而言,虽然我希望你现在已经升级到3.x了......不是没有一些解决方法.Blogger
我找到了2个似乎有效的解决方法(在Rails 3.2.8中).要理解为什么他们的工作,你应该先了解代码,其中,将提高该错误.
在我的activerecord版本(3.2.8)中,belongs_to
可以找到处理为关联分配嵌套属性的代码,lib/active_record/nested_attributes.rb:332
如下所示:
def assign_nested_attributes_for_one_to_one_association(association_name, attributes, assignment_opts = {})
options = self.nested_attributes_options[association_name]
attributes = attributes.with_indifferent_access
if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) &&
(options[:update_only] || record.id.to_s == attributes['id'].to_s)
assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy], assignment_opts) unless call_reject_if(association_name, attributes)
elsif attributes['id'].present? && !assignment_opts[:without_protection]
raise_nested_attributes_record_not_found(association_name, attributes['id'])
elsif !reject_new_record?(association_name, attributes)
method = "build_#{association_name}"
if respond_to?(method)
send(method, attributes.except(*unassignable_keys(assignment_opts)), assignment_opts)
else
raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?"
end
end
end
Run Code Online (Sandbox Code Playgroud)
在if
语句中,如果它看到您传递了用户ID(!attributes['id'].blank?
),它会尝试user
从博客的user
关联中获取现有记录(record = send(association_name)
其中association_name是:user
).
但由于这是一个新构建的Blogger
对象,blogger.user最初将是nil
,因此它不会assign_to_or_mark_for_destruction
在处理更新现有的分支中进行调用record
.这是我们需要解决的问题(参见下一节).
所以它转移到第一个else if
分支,它再次检查是否存在用户ID(attributes['id'].present?
).它存在,所以它检查下一个条件,即!assignment_opts[:without_protection]
.
既然你与初始化新的Blogger的对象Blogger.new(params[:blogger])
(也就是不及格as: :role
或without_protection: true
),它使用默认assignment_opts
的{}
.!{}[:without_protection]
是的,所以它继续raise_nested_attributes_record_not_found
,这是你看到的错误.
最后,如果其他2个if分支都没有被采用,它会检查是否应该拒绝新记录,并且(如果没有)继续构建新记录.这就是你提到的"创建一个全新的用户,如果它还不存在"的例子.
without_protection: true
我想到的第一个解决方法 - 但不建议 - 是使用without_protection: true
(Rails 3.2.8)将属性分配给Blogger对象.
Blogger.new(params[:blogger], without_protection: true)
Run Code Online (Sandbox Code Playgroud)
这样它就会跳过第一个elsif
并转到最后一个elsif
,这将构建一个具有params所有属性的新用户,包括 :id
.实际上,我不知道是否会导致它更新现有用户记录,就像你想要的那样(可能没有真正测试过那个选项),但至少它避免了错误...... :)
self.user
在user_attributes=
但我推荐的解决方法是实际初始化/设置user
来自:id param 的关联,以便使用第一个if
分支,并且它会像你想要的那样更新内存中的现有记录......
accepts_nested_attributes_for :user
def user_attributes=(attributes)
if attributes['id'].present?
self.user = User.find(attributes['id'])
end
super
end
Run Code Online (Sandbox Code Playgroud)
为了能够覆盖那样的嵌套属性访问器并调用super
,你需要使用边缘Rails或者包含我在https://github.com/rails/rails/pull/2945上发布的猴子补丁..或者,您可以assign_nested_attributes_for_one_to_one_association(:user, attributes)
直接从您的user_attributes=
setter呼叫而不是呼叫super
.
在我的情况下,我最终决定我不希望人们能够从这个表单更新现有用户记录,所以我最终使用了上面的变通方法的略微变化:
accepts_nested_attributes_for :user
def user_attributes=(attributes)
if user.nil? && attributes['id'].present?
attributes.delete('id')
end
super
end
Run Code Online (Sandbox Code Playgroud)
这种方法也可以防止错误发生,但这样做有点不同.
如果id在params中传递,而不是使用它来初始化user
关联,我只是删除传入的id,以便它将回退到new
从其他提交的用户参数构建用户.
尝试在嵌套表单中为用户 ID 添加隐藏字段:
<%=user_form.hidden_field :id%>
Run Code Online (Sandbox Code Playgroud)
嵌套保存将使用它来确定它是用户的创建还是更新。
归档时间: |
|
查看次数: |
4030 次 |
最近记录: |