I have a model A und a model B and the relation is A has_many B (and B belongs_to A). I have a model, a controller and views for A and only a model for B. I want to create and edit the instances of B on the edit view of A ( url/a/1/edit).
I know I can create a controller for B and call those methods using a form in the view of A, but then I need to redirect back to the view of A, because I don't want actual views for B.
Is there a recommended way to do this? What I want is to not break any of the helpers rails provides (e.g. after a forward I think it's a pain to get error messages and stuff like that).
Thanks in advance!
On the model level you would use accepts_nested_attributes_for
.
class A < ApplicationModel
has_many :bs
accepts_nested_attributes_for :bs
validates_associated :bs
end
class B < ApplicationModel
belongs_to :a
end
This lets A take attributes and create nested bs
by passing the attribute bs_attributes
with an array of attributes. validates_associated
can be used to ensure that A cannot be persisted of the bs
are not also valid.
To create the nested form fields use fields_for
<%= form_for(@a) do |f| %>
# field on A
<%= f.text_input :foo %>
# creates a fields for each B associated with A.
<%= f.fields_for(:bs) do |b| %>
<%= b.text_input :bar %>
<% end %>
<% end %>
To whitelist nested attributes use a hash key with an array of permitted attributes for the child records:
params.require(:a)
.permit(:foo, bs_attributes: [:id, :bar])
When creating new records you also have to "seed" the form if you want there to be inputs present for creating nested records:
class AsController < ApplicationController
def new
@a = A.new
seed_form
end
def create
@a = A.new(a_params)
if @a.save
redirect_to @a
else
seed_form
render :new
end
end
def update
if @a.update(a_params)
redirect_to @a
else
render :edit
end
end
private
def seed_form
5.times { @a.bs.new } if @a.bs.none?
end
def a_params
params.require(:a)
.permit(:foo, bs_attributes: [:id, :bar])
end
end
Edit:
seed_form can also just add one and do that every time. So you will always have one "empty" one to add. You need to make sure to filter out the empty one before saving if it was not filled by changing the accepts_nested_attributes_for
to:
accepts_nested_attributes_for :bs, reject_if: proc { |attr| attr['bar'].blank? }