5. Writing Methods for Updating and Deleting Post

As a user, I should be able to not only create posts, but to update them, delete them, and view individual posts. Luckily, when we put resources :posts in our routes.rb, Rails automatically created routes that do exactly this.

Let's start off with adding functionality so that users can update their posts.

First, let's create an edit method in our posts_controller.rb.

def edit
  @post = Post.find(params[:id])
end

What Post.find(params[:id]) is doing is, it's finding a Post with an id that matches the params[:id].

Before resuming, take 10 minutes to review the find method in Ideator - Editing and Updating Ideas.

So let's zoom back into our code. We have Post.find(params[:id]). What this means is that it's trying to find the post with the id that's passed in as a parameter.

Alright, let's move to the next step. Let's create an edit.html.erb file in app/views/posts. At the top of the file, let's add the text "Edit Post".

<h1>Edit Post</h1>

Before we move on, let's add a link to the edit page for each post. In our index.html.erb file, let's add in the following line of code to the loop we wrote in the previous lesson:

<div class="mt-4">
  <%= link_to 'Edit Post', edit_post_path(post.id), class: 'text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800' %>
</div>

Your loop should now look something like this:

<% @posts.each do |post| %>
  <%= image_tag post.user.photo if post.user.photo.attached? %>
  <%= post.user.email.split('@').first %>
  <%= image_tag post.photo %>
  <%= post.description %>

  <div class="mt-4">
    <%= link_to 'Edit Post', edit_post_path(post.id), class: 'text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800' %>
  </div>
<% end %>

Save the file and refresh the page. You should now see an edit link that works! However, we don't want to show this if the post isn't a post that was created by the current_user. Let's add a conditional to make sure that other users can't see this link.

<% @posts.each do |post| %>
  <%= image_tag post.user.photo if post.user.photo.attached? %>
  <%= post.user.email.split('@').first %>
  <%= image_tag post.photo %>
  <%= post.description %>

  <% if post.user == current_user %>
    <div class="mt-4">
      <%= link_to 'Edit Post', edit_post_path(post.id), class: 'text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800' %>
    </div>
  <% end %>
<% end %>

Here, we check to see if the post's user matches the current_user.

Cool. The next step is easy. All we have to do is copy the form that we created using simple_form in new.html.erb and paste it into our edit.html.erb.

<h1>Edit Post</h1>

<%= simple_form_for @post do |f| %>
  <%= f.input :photo %>
  <%= f.input :description %>
  <%= f.submit 'Post' %>
<% end %>

The submit button says "Post", but since that doesn't really make sense because we're updating the post instead of posting one, let's change the text to "Update".

<h1>Edit Post</h1>

<%= simple_form_for @post do |f| %>
  <%= f.input :photo %>
  <%= f.input :description %>
  <%= f.submit 'Update' %>
<% end %>

Save the file and refresh the page, and you should see the form. Awesome! However, if you try to submit the form, it will give you an error. This is because we haven't set up the update method yet. Let's go ahead and do that.

Back to our posts_controller.rb! Beneath our edit method, let's add the following lines of code:

def update
  @post = Post.find(params[:id])
  @post.update(post_params)
  if @post.valid?
    redirect_to root_path
  else
    render :edit, status: :unprocessable_entity
  end
end

This is mostly the same as our create action and you should be able to understand most of what's going on right now. If you need a refresher, refer back to this lesson :)

Awesome, now if we submit the form, it should update your post!

Let's move on to enabling the user to destroy their posts. The steps in order to do this is quite similar to what we did with the edit posts functionality that we just created.

In our posts_controller.rb, let's create a destroy method.

def destroy
  @post = Post.find(params[:id])
  @post.destroy
  redirect_to root_path
end

The first line, we should already understand. @post is assigned to the Post with an id of the params[:id] (refer back to Ideator - Editing and Updating Posts. The next line is simply saying that we want to destroy that post. Finally, the last line redirects the user to the root_path.

Let's go ahead and add the link so that we can test this out. Right now, our index.html.erb contains this loop:

<% @posts.each do |post| %>
  <%= image_tag post.user.photo if post.user.photo.attached? %>
  <%= post.user.email.split('@').first %>
  <%= image_tag post.photo %>
  <%= post.description %>

  <% if post.user == current_user %>
    <div class="mt-4">
      <%= link_to 'Edit Post', edit_post_path(post.id), class: 'text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800' %>
    </div>
  <% end %>
<% end %>

Let's go in our index.html.erb file and add a link to delete the post, if you are the owner of the post. Let's add the following line of code:

 <%= link_to 'Delete Post', post_path(post.id), method: :delete, class: 'text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800' %>

Notice how we added method: :delete. This specifies that we want to perform a DELETE request rather than a GET or POST request. (By default, link_to creates a GET request.)

Your loop should now look like this:

<% @posts.each do |post| %>
  <%= image_tag post.user.photo if post.user.photo.attached? %>
  <%= post.user.email.split('@').first %>
  <%= image_tag post.photo %>
  <%= post.description %>

  <% if post.user == current_user %>
    <div class="mt-4">
      <%= link_to 'Edit Post', edit_post_path(post.id), class: 'text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800' %>
      <%= link_to 'Delete Post', post_path(post.id), method: :delete, class: 'text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800' %>
    </div>
  <% end %>
<% end %>

Save the file and refresh the page. You should now see the delete link for each post. Awesome!

There's one last thing we need to do. Although we added a conditional in our loop in index.html.erb to make sure that other users can't see the link to edit and delete the post, people could still manually go to the Edit Posts URL and try to update the post. For example, someone could go to http://localhost:3000/posts/1/edit and they would be able to edit a Post with the id of 1. We need to make sure that we don't allow this to happen.

In the next lesson, we're going to implement our first filter to make this happen.

Lesson list