Let’s say you’d like to successfully create or update a nested form using fields_for
. Our ‘parent’ will be a Post model and our ‘child’ will be a Tag model. It’s not hard, but there’s a bit of a gotcha that I found most other resources out there were leaving out.
We’ll use a Post model as our parent in this situation. Nothing out of the ordinary here. A post can have many tags and we’ll allow them to be destroyed if the model itself is.
# app/models/post.rb
class Post < ActiveRecord::Base
has_many :tags, dependent: :destroy
# Enable the building of complex forms
accepts_nested_attributes_for :tags, allow_destroy: true
end
It’s important to include accepts_nested_attributes_for
to allow you to save attributes on the associated Tag model. You get no Rails magic if you forget this.
Again, nothing special here. Just showing that it belongs to our post model
# app/models/tag.rb
class Tag < ActiveRecord::Base
belongs_to :post
end
Now we get to some of the important stuff. There are a few things to take note of in the new and edit actions.
# /app/controllers/posts_controller.rb
def new
@post = Post.new
# Ensure we have a blank tag to start with
1.times { @post.tags.build }
end
def edit
@post = Post.find(params[:id])
1.times { @post.tags.build }
end
private
def post_params
params.require(:post).permit(:title, :body, tags_attributes:[:id, :title])
end
So, a few things here.
Tag
object with 1.times { @post.tags.build }
so the our form view doesn’t bork on us.:title
nested attribute for tags_attributes
, but perhaps not so obvious you should include :id
. After all, we don’t explicitly include :id
for our parent model.We’re going to follow the common pattern of sharing a _form.html.erb
partial between the new and edit views.
<!-- app/views/admin/posts/_form.html.erb -->
<%= form_for [:admin, @post] do |f| %>
<%= render '/shared/form_errors', object: f.object %>
<fieldset>
<%= f.text_field :title, class:'form-control', placeholder:'Title' %>
</fieldset>
<fieldset>
<%= f.text_area :body, class:'form-control', placeholder:'Body', rows:10 %>
</fieldset>
<h3>Post Tags</h3>
<%= f.fields_for :tags do |tag_form| %>
<fieldset>
<%= tag_form.text_field :title, class:'form-control' %>
</fieldset>
<% end %>
<fieldset>
<%= f.submit "Save", class:'btn' %>
</fieldset>
<% end %>
Now it should be obvious why we had to build an empty Tag
object. If we hadn’t, we would have an error with tag_form.text_field :title
when on the new view because tag_form
would be null
.
The fact that we’re using 1.times { @post.tags.build }
in the edit
action means we’ll always have an extra blank field should we want to add an additional tag.
Creating nested forms in Rails isn’t overly complicated. I just got hung up on the necessary Strong Parameters to permit, namely the nested :id
.
Written by Matt Haliski
Consumer of tacos