Handling Multiple Model Objects in a Single Rails Form
I have been working on a flight booking project that required me to dynamically create passengers upon booking a flight. This led me to a painstakingly interesting journey of learning about nested forms in rails. In this blog, we'll explore how to handle forms in Rails that work with more than one model object, focusing on nested forms.
Forms are an essential part of web applications, allowing users to submit data that gets processed on the backend. In Ruby on Rails, handling forms for a single model is straightforward using form_with
or form_for
. However, there are cases where you need to submit data to multiple models in one form, such as when creating a Post
with multiple associated Comment
objects.
What Are Nested Forms?
Nested forms allow you to create or update a parent object and its associated child objects in a single form submission. This technique is extremely useful when you have a one-to-many or many-to-many relationship between models.
Example Scenario
Imagine you're building a blog where a user can create a post and add multiple comments at the same time. You want a single form that allows the user to:
Enter the post's title and body.
Add one or more comments.
Let’s dive into how to implement this.
Step 1: Set Up the Models and Associations
First, we'll create the Post
and Comment
models with a one-to-many relationship. A Post
can have many Comments
, and a Comment
belongs to a Post
.
# app/models/post.rb
class Post < ApplicationRecord
has_many :comments, inverse_of: :post
accepts_nested_attributes_for :comments, allow_destroy: true
end
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :post
end
In the Post
model:
has_many :comments
defines the one-to-many relationship.accepts_nested_attributes_for :comments
allows the form to handle attributes forcomments
along withpost
.
The allow_destroy: true
option will let us remove comments directly from the form if needed.
Step 2: Create the Controller
Next, we need to modify the controller to handle the nested attributes. In the PostsController
, we need to build comments when initializing a new post, and permit nested attributes for comments
in the post_params
.
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def new
@post = Post.new
@post.comments.build # Builds an empty comment for the form
end
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post, notice: 'Post and comments were successfully created.'
else
render :new
end
end
private
def post_params
params.require(:post).permit(:title, :body, comments_attributes: [:id, :content, :_destroy])
end
end
Here:
In the
new
action, we call@
post.comments.build
to ensure the form displays fields for a new comment.In the
post_params
, we permitcomments_attributes
, which will allow nested data to be passed for the associated comments.
Step 3: Build the Form
Now that the models and controller are set up, we can create the form that will allow the user to enter both the post details and associated comments.
<!-- app/views/posts/new.html.erb -->
<%= form_with(model: @post, local: true) do |form| %>
<%= form.label :title %>
<%= form.text_field :title %>
<%= form.label :body %>
<%= form.text_area :body %>
<h3>Comments</h3>
<%= form.fields_for :comments do |comment_form| %>
<div class="comment-fields">
<%= comment_form.label :content, "Comment" %>
<%= comment_form.text_area :content %>
</div>
<% end %>
<%= form.submit 'Create Post and Comments' %>
<% end %>
Explanation:
We use
form_with
to generate the form for thePost
model.Inside the form,
fields_for :comments
is used to create fields for the associatedComment
objects. This tells Rails that we’re accepting nested attributes for comments.The form will display fields for entering the post's title, body, and one comment.
Step 4: Handling Multiple Comments
To handle more than one comment in the form, we need to allow the user to add multiple comments. This can be done by building multiple comments
in the new
action of the PostsController
.
# app/controllers/posts_controller.rb
def new
@post = Post.new
3.times { @post.comments.build } # Builds three empty comments for the form
end
In this case, we build three empty comments, so the form will display fields for three comments. You can adjust the number of comments as needed.
In the view, you don’t need to change anything from the previous form example. fields_for
will iterate through each of the built comments and render form fields for them.
<!-- app/views/posts/new.html.erb -->
<%= form_with(model: @post, local: true) do |form| %>
<%= form.label :title %>
<%= form.text_field :title %>
<%= form.label :body %>
<%= form.text_area :body %>
<h3>Comments</h3>
<%= form.fields_for :comments do |comment_form| %>
<div class="comment-fields">
<%= comment_form.label :content, "Comment" %>
<%= comment_form.text_area :content %>
</div>
<% end %>
<%= form.submit 'Create Post and Comments' %>
<% end %>
Now, when the user submits the form, all the entered comments will be saved along with the post.
Conclusion
Handling multiple model objects in a single Rails form can be done effectively using nested forms. By leveraging accepts_nested_attributes_for
in the model, Rails allows you to seamlessly handle parent-child relationships through forms.
In this example, we covered:
Setting up associations between models.
Building the necessary logic in the controller to handle nested attributes.
Creating a form that submits data for both the
Post
and multipleComments
.
This approach is especially useful when dealing with complex forms involving parent-child relationships, making your Rails applications more powerful and user-friendly.
Subscribe to my newsletter
Read articles from Gaetano Osur directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by