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?
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 inids
. This method loads the models and callscollection=
.
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