Implement the Ability to Edit Database Records from Scratch in Rails
This lesson walks through how to build out the ability to edit database records in Rails, completely from scratch.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

In the last guide we discovered how to build out our new and create actions. We did a bit of code writing in that. Hopefully you don’t feel too overwhelmed by all that we covered. I’m happy to announce that what we discuss in this guide is very similar to the previous one. So, if you feel like you're in a bit of a blur, don't worry as we're going to do a lot of repetitive action in this guide, and hopefully that will solidify your understanding of the processes we are looking to implement.

So, the next set of features we'll be building in are the edit and update feature.

A Method with a View

Based on our experience with building out the new feature, we know we're going to need an edit view page. So, go to views/portfolios folder and create a file called edit.html.erb.

Next I’m going to take a step that we will have to come back and fix later on. Basically, I’m going to copy everything from new.html.erb and paste it in edit.html.erb. The only difference is I will change the heading to "Edit this Portfolio Item".

<h1>Edit this Portfolio Item</h1>

<%= form_for(@portfolio_item) do |f| %>
  <div class="field">
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>

  <div class="field">
    <%= f.label :subtitle %>
    <%= f.text_field :subtitle %>
  </div>

  <div class="field">
    <%= f.label :body %>
    <%= f.text_area :body %>
  </div>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

It's definitely bad practice to copy and paste this and have duplicate code throughout your application, so later, I will teach you to create partials in order to have shared files. Eventually, our new and edit files will only have a title, followed by a call to this form as a partial. This will follow the convention of the scaffold as well. For now, we'll leave it as it is because we're focussing on pure data flow, and we know that our edit action needs to have an an edit form.

Next, we need to create an edit method in our portfolio_controller file.

def edit
end

The code inside edit is going to be a little different from new. In new, we are creating a new item from scratch, but here we're going to edit an existing item. To illustrate this, go to your terminal and use the command rake routes.

large

Our portfolio route is specialized in regard to edit. When you look at the edit portfolio line, specifically the URI pattern, /portfolio/:id/edit notice the :id. This means, we have to pass in an id, so portfolios knows which instance to edit.

I think that is quite logical. When we created a new instance we didn't have anything to look up. But, when you're editing an item, the system can't randomly choose which item to edit it. It needs to be told which instance and the start point for that is right in the route. So, inside of the route we are going to extract that id. We have a little bit of a hint on how to do that. If you go to blogs_controller.rb, you'll see a method called set_blog.

    def set_blog
      @blog = Blog.find(params[:id])
    end

Remember, this method is called by the before_action method for the show, edit, update and destroy functions.

before_action :set_blog, only: [:show, :edit, :update, :destroy]

I'm not going to create a separate method for this right now, as we'll do the refactoring later. For now, I'm going to pattern this after the set_blog method and have

def edit
  @portfolio_item = Portfolio.find(params[:id])
end

Essentially, params[:id] is the value of the id in the URI. So, when you go to a application and you type in something like

localhost:3000/portfolio/1/edit

the system is going to look at the params and determine it needs to find the portfolio with that particular id.

In this instance the value is 1. Accordingly, the system will then pull up the portfolio with the corresponding id. To be clear, the params id will be whatever the value in the URI is. It could be 1 it could be 5 it could be 5000. Whatever the id is, it will be passed into the .find method and the system is going to find that instance to edit.

Querying an Item

This part a little ambiguous because we haven't yet discussed how to querying items.

Go ahead and start the rails console with the command rails c We will discover what this looks like in a core text basis before we go into the application.

Here in the rails console there are a few ways to find a portfolio item. The command Portfolio.last will bring up the last portfolio item created. This is a great way to search for debugging purposes or just to see what the last item in the database is.

Usually though you want to find a specific item. To do this we can use the .find method. By default, the way Rails works, is that if we just use find it will look the instance up by id. So, if we pass in Portfolio.find(5), Rails will look for the portfolio item that has the id of 5.

In the result, you can see that it queried the database, you can even see the SQL code that was used. It looked for the portfolio items in the portfolios database table, where the portfolios id is equal to 5, just like we searched. Then it brought back the item and now we can use it. This is essentially what's happening in our application as well.

Before we get into editing, let’s just see if we’ve done everything correctly in our application. In our portfolios_controller we have our edit method that list our portfolio item. This matches the portfolio item in our views/portfolios/edit.html.erb form_for method and all of our portfolio attributes are present in the form, and our portfolio item is equal to whatever portfolio is brought back with the id. That looks like all that we need.

Start the rails server rails s and go to the browser. If you type "localhost:3000/portfolios/5/edit", you'll see the Edit this Portfolio Item page and you are going to see something really cool that the form_for method does for you.

large

Not only did it pull up the correct portfolio item, Rails is smart enough to perform a sub database query that populates the data inside the edit form. So, all the data in the database is plugged into the appropriate fields in our browser, and this is possible because of the form_for method. If you look at the source code for form_for, you'll observe that it maps the attributes such as title, subtitle and body to the attributes in the database record.

Let's say I change the title elements to title_two like this:

<%= form_for(@portfolio_item) do |f| %>
  <div class="field">
    <%= f.label :title_two %>
    <%= f.text_field :title_two %>
  </div>

If you save your file and refresh the browser, you'll see that it throws an error. undefined method ‘title_two’ for portfolio

This is because there is no method as title_two. The error message may seem confusing because title_two doesn't really seem like a method. But, because of the way Rails works, it treats this form_for as a database attribute lookup method.

Indeed, in regards to our portfolio item, Rails is going to pass all these attributes, title, subtitle and body to the record, and verify that it exists. If it does exists, it'll populate those text and area fields, in the browser.

Go ahead and fix your items to once again say title and then everything will work correctly.

And that is how you build your edit form and pre-populate all the values. When we get into our Forms section, I'm going to show you how to build all this from scratch. Some of this my appear a little on the magical side, and it is pretty impressive. However, in the Forms section, I'll show you how to create a HTML form and build out this same functionality. I think it's important to understand what the form_for method does for us, as it practical knowledge to have.

Building the Update Method

Now that we have our edit form working, let's work on the update method. Right now, if you try to make a change in the edit form by clicking on the "Update portfolio” button, it'll throw an error because we don’t have an update method yet.

Like we did before, we'll take the update method from our blogs_controller.rb and put it in our portfolios_controller.rb.

 def update
    respond_to do |format|
      if @blog.update(blog_params)
        format.html { redirect_to @blog, notice: 'Blog was successfully updated.' }
      else
        format.html { render :edit }
      end
    end
  end

Once again, we won’t have this redirect to the show page, instead we will have it redirect to the portfolios redirect_to portfolios_path. We’ll change the notice as well to say the record was successfully updated.

- format.html { redirect_to @blog, notice: 'Blog was successfully updated.' }
format.html { redirect_to portfolios_path, notice: 'The record was successfully updated.' }

Also, our params need to be identical to what we have for our create method.

- if @blog.update(blog_params)
+ if @blog.update(params.require(:portfolio).permit(:title, :subtitle, :body))

Hopefully, you can understand why it is important to have strong params in another method. You can see we have the exact duplicate code here in our portfolios_controller for both the create and update methods. A rule of thumb is, when you have identical code in multiple locations of a file, it's best to put them in a separate method. This is why you have these methods in the controller generated by the scaffold:

# Use callbacks to share common setup or constraints between actions.
def set_blog
    @blog = Blog.find(params[:id])
end

# Never trust parameters from the scary internet, only allow the white list through.
def blog_params
    params.require(:blog).permit(:title, :body) end

It's definitely a better practice to use these separate methods. You’re not going to see a lot of duplicate code in the blogs_controller as it's been abstracted out into its own method. This is a very common pattern in Rails development.

For now, we'll leave things in our portfolios controller as they are, without defining these separate methods. However, we can't have our update method similar to the create method because for create we are using the .new method. For the update method we are using .find so we need to look the item up, therefore we need an id.

If you go to blogs_controller.rb, you'll see that the set_blog method will run for update action (as well show, edit, and destroy). To duplicate that action, let's copy the code from our edit method @portfolio_item = Portfolio.find(params[:id]) and paste it at the beginning of our update method. This is going to be completely identical and the reason we will eventually put @portfolio_item = Portfolio.find(params[:id]) into it’s own method and then remove the messy duplicate code.

Last, we need to fix the update

if @blog.update(params.require(:portfolio).permit(:title, :subtitle, :body))
if @portfolio_item.update(params.require(:portfolio).permit(:title, :subtitle, :body))

For now, this is what your current methods should look like:

def edit
    @portfolio_item = Portfolio.find(params[:id])
  end

  def update
    @portfolio_item = Portfolio.find(params[:id])
    respond_to do |format|
      if @portfolio_item.update(params.require(:portfolio).permit(:title, :subtitle, :body))
        format.html { redirect_to portfolios_path, notice: 'The record successfully updated.' }
      else
        format.html { render :edit }
      end
    end
  end

Let's try to update the record. Save your file and then head the the browser and refresh your portfolio edit page. Edit the item and hit update and it should work perfectly . If you scroll down, you can see the updated record at the bottom of your list.
Now, you may notice that we don't have a way of editing the file, except by going to the browser and typing the entire URL, including the id and the edit path. That's not a good user experience, so in the next guide, I'm going to show you how to build an edit button that'll take the user from here to the edit page.
Lastly, let's push the files to our GitHub branch.

git add .

git commit -m ‘Implemented edit and update functionality’

git push origin portfolio-feature

Good job working through this guide.