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
class Post < ApplicationRecord
belongs_to :category
belongs_to :author
end
class Category < ApplicationRecord
has_many :posts
has_many :authors, through: :posts
end
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
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 %>
3) In the child controller, define strong params.
private
def post_params
params.require(:post).permit(
:content,
:category_id,
:author_id
)
end
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
*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
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 %>
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
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 %>
4) see Ex.2
5) see Ex.1
Top comments (0)