Using form_with With Nested Resources in Ruby on Rails
In Ruby on Rails Jan 14, 2022
Updated on May 13, 2023
In this post, we will deal with using the form_with helper with nested resources in Ruby on Rails. We will build a blog application, and the blog posts will have comments. We will nest the two resources and will create a comment form partial that we will use to create a new comment and edit existing comments. We will use Rails 7.0.1 and Ruby 3.1.0.
Set Up the Application
We start by creating a new app. From the terminal, we type:
rails new my_blog
Then we change the directory to the application we just created:
cd my_blog
Generate A Scaffold For the Post Resource
In the terminal, we type:
bin/rails g scaffold Post title:string content:text
Let's run the migration:
bin/rails db:migrate
Set the Root Route
Let's set the root
route to the blog
index
action. We put the root
route at the top of the Rails.application.routes.draw do
block in config/routes.rb
file:
root 'posts#index'
Generate a Comment Model
For the other resource, we will generate a model. In the terminal type:
bin/rails g model Comment name:string comment:text
After that, we run the migration:
bin/rails db:migrate
Associate the Two Models
First, let's generate the migration to add a post_id
to the comment
model:
bin/rails g migration AddPostRefToComment post:references
Let's run the migration:
bin/rails db:migrate
Then we edit the post.rb
file, respectively the comment.rb
file in the
app/models/
folder, to reflect the association between the two models.
In the post.rb
file, we declare a has_many
association:
has_many :comments, dependent: :destroy
In the comment.rb
file, we declare the corresponding belongs_to
association:
belongs_to :post
Create a Comments Controller
We generate a Comments
controller by typing the following command in the terminal:
bin/rails g controller Comments create edit update
Let's edit the controller. First, we specify the strong parameters in the private method comment_params
:
private
def comment_params
params.require(:comment).permit(:name, :comment, :post_id)
end
Next, we'll edit the create
, update
and edit
methods:
def create
@comment = Comment.new(comment_params)
if @comment.save
redirect_to post_path(@comment.post), notice: 'Your comment has been sent successfully.'
else
redirect_back fallback_location: root_path , notice: 'Something went wrong.'
end
end
def edit
@comment = Comment.find(params[:id])
end
def update
@comment = Comment.find(params[:id])
if @comment.update(comment_params)
redirect_to post_path(@comment.post), notice: 'Comment was successfully updated.'
else
render :edit
end
end
Nest the Resources
We will now edit the config/routes.rb
file to nest the two resources. First, we delete the lines:
get 'comments/create'
get 'comments/edit'
get 'comments/update'
Then we nest the two resources as shown below:
resources :posts do
resources :comments, only: [:create, :edit, :update]
end
Create the Comment Form
It is time to create the comment
form. In our code editor, let's go to the app/views/
folder
and create the _form.html.erb
partial. We also delete the auto-generated create.html.erb
and
update.html.erb
files, because we don't need them.
Let's now write the code to generate the comment
form:
<%= form_with(model: [@comment.post, @comment], local: true) do |form| %>
<p>
<%= form.label :name %>
<%= form.text_field :name %>
</p>
<p>
<%= form.label :comment %>
<%= form.text_area :comment %>
</p>
<%= form.hidden_field :post_id %>
<p>
<%= form.submit %>
</p>
<% end %>
Since the resources are nested, we need to pass both models to the form_with
helper, first the parent,
then the child.
Display the Comment Form on the Post View
We render the comment
form partial on the post
view at the bottom:
<h2>Leave a reply:</h2>
<%= render partial: 'comments/form' %>
For this form to work, we need to make a @comment
instance variable available in the show
method in the Posts
controller:
@comment = @post.comments.build
The build
method will create a new comment
in memory.
Edit the Comment
We use the same app/views/comments/_form.html.erb
partial in the comment
edit
view. In the app/views/posts/edit.html.erb
file, we delete the auto-generated code and put these lines
instead:
<h2>Editing Comment</h2>
<%= render 'form' %>
Display a List of Comments
We want to display a list of comments on the post view. First, we need to make a @comments
instance
variable in the show
method in the app/controllers/posts_controller.rb
:
@comments = @post.comments.all
In the show.html.erb
file in the app/views/posts/
folder, we add a list of comments after
the comment
form:
<ul>
<% @comments.each do |comment| %>
<li>
<p><%= comment.name %></p>
<p><%= comment.comment %></p>
<p> <%= link_to 'Edit', edit_post_comment_path(@post, comment) %>
</li>
<% end %>
</ul>
When using the _path
helper, we must pass both the @post
and the comment arguments, since we
deal with nested resources.