Theory of Coding

Web Development blog

Nested Forms

Nested Forms with RAILS

Nested forms is a form within another form. It’s nice to not jump between the parent and child forms when creating both the parent and the child in the same form. In this example, I’m using Post as a parent and Tags as the child. A Post will have many Tags.

accepts_nested_attributes_for

There is a cool method accepts_nested_attributes_for from ActiveRecord that allows you to save attributes on associated records through the parent. This method gives you the attribute writer, in this case, we got the tags_attributes=(attributes) writer method. You can add allow_destroy: true so if the post is deleted, this tag will be deleted also.

1
2
3
4
class Post < ActiveRecord::Base
  has_many :tags, :through => :post_tags
  accepts_nested_attributes_for :tags, allow_destroy: true
end

The Form

We made form_for for the new Post. We want to make a nested form so that we can create a form for a new Tag also. To do this, we will use fields_for to make a text_field to enter a new tag. We will be making a Tag.new in place for the new tag.

1
2
3
4
5
6
7
8
<%= form_for(@post) do |f| %>
  ..... #other code on the form 
 <%= f.fields_for :tags, Tag.new do |tag_field| %>
  <%= tag_field.label :name %>
  <%= tag_field.text_field :name %>
  <% end %>

 <%= f.submit %>

Params

Imgur

See how if you inspect element, this new tag text field is called tags_attributes? This came from the cool method accepts_nested_attributes_for in the Post model. This automatically gives you nested params post[tags_attributes][0][name]. Although we did fields_for :tags, it gives you tags_attributes in the params for free. It is also [0] because this is the first tag we’re making, but you can certainly enter more tag inputs if you wish.

To allow this in our params, we will do add tags_attributes with the name field.

1
2
3
4
private
def post_params
    params.require(:post).permit(:name, :content,:tags_attributes =>[:name],:tag_ids => [])
end

As a reminder, we made post_params as a private method so that we can do mass assignment, and prevent a hacker from editing the params and changing something in our forms that we didn’t want to change.

Now this will allow us to get a nested hash in our params that’s part of Post. "tags_attributes"=>{"0"=>{"name"=>"mrkittycat"}}},

1
2
3
4
5
6
7
8
9
10
{"utf8"=>"✓",
 "authenticity_token"=>"r/vgXWbzduyZIQxVetNzcDJEt3CbDX4Js2oclxXYq5eUjmZdoRCB+PP17xoBPuIQkJPHCWRzmaBTQ/1vH6WfIA==",
 "post"=>
  {"name"=>"The new post name",
   "content"=>"yay writing a new post",
   "tag_ids"=>["1", "100", ""],
   "tags_attributes"=>{"0"=>{"name"=>"mrkittycat"}}},
 "commit"=>"Create Post",
 "controller"=>"posts",
 "action"=>"create"}

def create

Now that we got the params we want, we have to actually create the new tag.

Once the form is submitted, this will go to the def create method of the Post controller, because it is sending a post request to the server.

1
2
3
4
def create
  @post = Post.new(post_params)
  #other code here
end

We will do Post.new and put in the post_params from our private method, and it will set its attributes to whatever we provided in the params.

It will ALSO set the :name to the newly created tag, and associate this tag to this particular post. This was made possible because of the accepts_nested_attributes_for method in our Post model.

Imgur

Great! Now we have the tag that we just created– mrkittycat.

That’s the gist of it. This allowed me to create a new tag and associate it with this post, without making separate forms. It comes in handy at times.

To read more about nested forms, here is some documentation about it from ApiDock