DEV Community

Alexander Rovang
Alexander Rovang

Posted on

Notes to Myself (Rails:Entry#2a - Nested Forms)

This is a step-by-step instructional doc to use when creating nested forms. It will cover belongs_to, has_many, and has_many_through associations.

The example models I will be using are Author, Posts, and Categories.

class Author < ApplicationRecord
  has_many :posts
  has_many :categories, through: :posts
end
Enter fullscreen mode Exit fullscreen mode
class Post < ApplicationRecord
  belongs_to :category
  belongs_to :author
end
Enter fullscreen mode Exit fullscreen mode
class Category < ApplicationRecord
  has_many :posts
  has_many :authors, through: :posts
end
Enter fullscreen mode Exit fullscreen mode

BELONGS_TO

The first scenario is when you have a child instance that has a belongs_to relationship with a parent class (in this example the child is the Post class which has 2 parent classes: Author & Category), and we would like to create a new instance of the child object that is automatically associated with it's parent class.

There are 3 methods that will achieve this form. The first assumes that a parent class already exists. The second assumes that it does not exist. The third assumes that there are potential parent classes which exist, but the user may want to create their own.

The Parent Class Exists

1) In the child controller, create a 'dummy' instance to trigger the form_for builder. This will be the same for all three scenarios.

  def new
    @post = Post.new
  end
Enter fullscreen mode Exit fullscreen mode

2) On the child form page, use text_field for the child attributes and collection_select for the parent attributes.

<%= form_for @post do |f| %>
<div><%= f.label "Post Content" %>
     <%= f.text_field :content %></div>
<div><%= f.label "Author" %>
     <%= f.collection_select author_id, Author.all, :id, :name %></div>
<div><%= f.label "Category" %>
     <%= f.collection_select category_id, Category.all, :id, :name %></div>
     <%= f.submit %>
<% end %> 
Enter fullscreen mode Exit fullscreen mode

3) In the child controller, define strong params.

  private

  def post_params
    params.require(:post).permit(
      :content, 
      :category_id, 
      :author_id
    )
  end
Enter fullscreen mode Exit fullscreen mode

4) In the child controller, create the child object. This will also be the same for all three scenarios.

 def create
    Post.create(post_params)
    redirect_to posts_path
  end
Enter fullscreen mode Exit fullscreen mode

*special note - Author.all and Category.all (used on the form page) can & should be refactored in view helper

The Parent Class Does Not Exist

1) see Ex.1

2) In the child model, define a setter/getter method for the parent. This will be the same in the 3rd scenario.

def author_name=(name)
  self.author = Author.find_or_create_by(name: name)
end

def author_name
   self.author ? self.author.name : nil
end

def category_name=(name)
  self.category = Category.find_or_create_by(name: name)
end

def category_name
   self.category ? self.category.name : nil
end
Enter fullscreen mode Exit fullscreen mode

3) On the child form page, use text_field for all attributes of both child and parent.

<%= form_for @post do |f| %>
<div><%= f.label "Post Content" %>
     <%= f.text_field :content %></div>
<div><%= f.label "Author" %>
     <%= f.text_field :author_name %></div>
<div><%= f.label "Category" %>
     <%= f.text_field :category_name %></div>
     <%= f.submit %>
<% end %> 
Enter fullscreen mode Exit fullscreen mode

4) In the child controller, define strong params. This will be the same for the 3rd scenario.

  private

  def post_params
    params.require(:post).permit(
      :content, 
      :category_name, 
      :author_name
    )
  end
Enter fullscreen mode Exit fullscreen mode

5) see Ex.1

A Parent Class Exists, But You Want The Option To Create As Well

1) see Ex.1

2) see Ex.2

3) On the child form page, use text_field for the child attributes, and datalist for the parent.

<%= form_for @post do |f| %>
<div><%= f.label "Post Content" %>
     <%= f.text_field :content %></div>

<div><%= f.label "Author" %>
     <%= f.text_field :author_name, list: "authors_autocomplete" %>
          <datalist id="authors_autocomplete">
               <% Author.all.each do |author| %>
                 <option value="<%= author.name %>">
               <% end %>
          </datalist></div>

<div><%= f.label "Category" %>
     <%= f.text_field :category_name, list: "categories_autocomplete" %>
          <datalist id="categories_autocomplete">
               <% Category.all.each do |category| %>
                 <option value="<%= category.name %>">
               <% end %>
          </datalist></div>

     <%= f.submit %>
<% end %> 
Enter fullscreen mode Exit fullscreen mode

4) see Ex.2

5) see Ex.1

Top comments (0)