英文:
Rails: Child record created before parent and throws RecordNotFound error
问题
我有一个奇怪的工作流程,首先创建一个子记录,然后我希望通过嵌套表单将其与父记录关联。这是一个使用 rspec 的示例:
这是模型:
class Parent < ApplicationRecord
has_many :children, class_name: 'Child', dependent: :destroy
accepts_nested_attributes_for :children, allow_destroy: true, reject_if: :all_blank
end
class Child < ApplicationRecord
belongs_to :parent, foreign_key: 'child_id', optional: true
end
我的控制器是标准的:
def create
@parent = Parent.new(parent_params)
respond_to do |format|
if @parent.save
format.html { redirect_to @parent }
else
p @recipe.errors.full_messages
end
end
end
def parent_params
params.require(:parent).permit(
:name,
children_attributes: [
:id, :_destroy
]
)
end
由于子记录已经存在,我只是尝试将其与父记录关联起来,而不是创建一个全新的记录。这是我的测试,但它失败并显示错误:
child = Child.create
expect {
post "/parent", params: {
parent: {
name: "test",
children_attributes: [{
id: child.id
}]
}
}.to change(Parent.all, :size).by(+1)
.and change(Child.all, :size).by(0)
end
我收到的错误是:
ActiveRecord::RecordNotFound:
Couldn't find Child with ID=1 for Parent with ID=
显然,这是因为 Child
在 Parent
之前存在,但我仍然觉得应该有可能使其工作,使现有的子记录与保存的父记录关联起来。
英文:
I have a strange workflow whereby a child record is created first and then I want it associated with the parent through a nested form. Here is an example using rspec:
Here are the models:
class Parent < ApplicationRecord
has_many :children, class_name: 'Child', dependent: :destroy
accepts_nested_attributes_for :children, allow_destroy: true, reject_if: :all_blank
end
class Child < ApplicationRecord
belongs_to :parent, foreign_key: 'child_id', optional: true
end
My controller is standard:
def create
@prent = Parent.new(parent_params)
respond_to do |format|
if @parent.save
format.html { redirect_to @parent }
else
p @recipe.errors.full_messages
end
end
end
def parent_params
params.require(:parent).permit(
:name,
children_attributes: [
:id, :_destroy
]
)
end
Since the child already exists, I am only trying to associate it with the parent, not create an entirely new record. Here is my test that fails with an error:
child = Child.create
expect {
post "/parent", params: {
parent: {
name: "test",
children_attributes: [{
id: child.id
}]
}
}.to change(Parent.all, :size).by(+1)
.and change(Child.all, :size).by(0)
end
And the error I'm receiving is
ActiveRecord::RecordNotFound:
Couldn't find Child with ID=1 for Parent with ID=
Clearly it's because Child
exists before Parent
but I still feel this should be possible. Is there a way to get this to work such that the existing child is associated with the parent when the parent is saved?
答案1
得分: 3
If you only need to associate existing records when creating another, use child_ids=
method.
> collection_singular_ids=ids
> 使用 ids
替换集合中由主键标识的对象。该方法加载模型并调用 collection=
。
https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
# db/migrate/20230601025957_create_parents.rb
class CreateParents < ActiveRecord::Migration[7.0]
def change
create_table :parents
create_table :children do |t|
t.references :parent
end
end
end
# app/models/parent.rb
class Parent < ApplicationRecord
has_many :children
end
# app/models/child.rb
class Child < ApplicationRecord
belongs_to :parent, optional: true
end
# spec/requests/parent_spec.rb
require "rails_helper"
RSpec.describe "Parents", type: :request do
it "POST /parents" do
child = Child.create!
expect do
# 不要忘记允许 `child_ids: []`
post "/parents", params: {parent: {child_ids: [child.id]}}
end.to(
change(Parent, :count).by(1).and(
change(Child, :count).by(0)
)
)
end
end
# 1 个示例,0 个失败
在这里,children
不是一个连接表,但如果你设置了 through 关联,*_ids
方法也可以正常工作。类似于这样:https://stackoverflow.com/a/75972917/207090
英文:
If you only need to associate existing records when creating another, use child_ids=
method.
> collection_singular_ids=ids
> Replace the collection with the objects identified by the primary keys in ids
. This method loads the models and calls collection=
.
https://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
# db/migrate/20230601025957_create_parents.rb
class CreateParents < ActiveRecord::Migration[7.0]
def change
create_table :parents
create_table :children do |t|
t.references :parent
end
end
end
# app/models/parent.rb
class Parent < ApplicationRecord
has_many :children
end
# app/models/child.rb
class Child < ApplicationRecord
belongs_to :parent, optional: true
end
# spec/requests/parent_spec.rb
require "rails_helper"
RSpec.describe "Parents", type: :request do
it "POST /parents" do
child = Child.create!
expect do
# don't forget to permit `child_ids: []`
post "/parents", params: {parent: {child_ids: [child.id]}}
end.to(
change(Parent, :count).by(1).and(
change(Child, :count).by(0)
)
)
end
end
# 1 example, 0 failures
children
is not a join table here, but if you have through associations set up, *_ids
method will also just work. Something like this: https://stackoverflow.com/a/75972917/207090
答案2
得分: 0
accepts_nested_attributes_for
不具备您所需的功能,您只能创建新的子项或更新已关联子项的属性,无法添加现有子项。
如果存在 id
但尚未成为 parent.children
的一部分,您可以重写 children_attributes=
方法,以提供您所寻求的功能,然后将其传递给 super
以进行正常行为。
英文:
accepts_nested_attributes_for
does not have the feature you're after, you can only create new children, or update the attributes of already associated children, you can't add existing children.
You could override the children_attributes=
method so that it provided the functionality you were looking for if an id
existed but was not already part of the parent.children
(and then pass off to super
for the normal behavior).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论