Using form_with With Nested Resources in Ruby on Rails

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.

Post last updated on May 13, 2023