Build Ability to Edit Posts via BDD
In this guide, we are going to implement the ability to edit posts and we're going to build the feature by using BDD best practices.
Guide Tasks
  • Read Tutorial
  • Watch Guide Video
Video locked
This video is viewable to users with a Bottega Bootcamp license

In this guide, we are going to implement the ability to edit posts and we're going to build the feature by using BDD best practices.

Let's start with the post_spec.rb file in the features folder. First, add a new test case for editing a post. However, before we check if an edit page exists, we need to create a post. Here, let's use the create method and not build_stubbed as we have to touch the database to create a new post.

# spec/features/post_spec.rb

it 'can be reached by clicking edit on index page' do
  post = FactoryGirl.create(:post)
  visit posts_path

  click_link 'Edit'
  expect(page.status_code).to eq(200)
end

In this code, the test will:

  1. Create a post
  2. Visit the posts index page
  3. Click on the edit button
  4. Expect the page to have a status code of 200

If you run rspec, everything is passing. But, it shouldn't pass and should've thrown an error, so we're going to check why it didn't.

Let's take a backwards strategy. Start by going to _post.html.erb and create an option to edit every post with the code:

<!-- app/view/posts/_post.html.erb -->

<td>
  <%= link_to 'Edit', edit_post_path(post) %>
</td>

This will give the following output on the browser:

large

Now if you run rspec, it throws an error and that's just what we want!

So what is the problem with our test? The issue is that we're looking to find the status code 200. However all this means is that the page exists. Our test passed because technically it did fine a page, it just wasn't the one we wanted This is why it's important to be careful whenever you're testing HTTP status codes since they can be a bit misleading.

After implementing an "edit" link, the test case failed because when it clicked the link, the status code of the page was not 200 as edit_post_path was not available.

Now, let's quickly refactor the code. If you see, no ID is being passed to the "Edit" link, and this can make it difficult to identify which post needs to be edited. So, let's pass an ID. Go to _post.html.erb and pass an ID, like this:

<!-- app/view/posts/_post.html.erb -->

<td>
  <%= link_to 'Edit', edit_post_path(post), id: "edit_#{post.id}" %>
</td>

Now that we have the ID, let's refactor our test case. The problem with our existing code is that, if the test finds anything called "edit", it will click on that. This makes it fragile and can cause the test to have unexpected behavior. To prevent that, let's pass the ID to click_link method using some string interpolation.

# spec/features/post_spec.rb

it 'can be reached by clicking edit on index page' do
  post = FactoryGirl.create(:post)
  visit posts_path

  click_link("edit_#{@post.id}")
  expect(page.status_code).to eq(200)
end

The good thing about this code is that it won't break even if we end up changing the link to a button or an icon in the future.

Next, let's open our controller and have an edit action. We also have to add the edit option to our set_post before_action code.

#app/controllers/posts_controller.rb

before_action :set_post, only: [:show, :edit]

Next, let's create a view file called edit.html.erb. This should be enough to get our test passing. If you run rspec now, everything should work.

So, that was a pretty basic test which only checks if the edit page exists. Now, let's jump into the functionality with another test case.

This code will visit the page, fill the form with new data and hit save. Then, it will expect the page to have the new content.

As expected, the code breaks and says it cannot find a field called date. Essentially, it means it cannot find a form and we have to create one now in edit.html.erb.

<!-- app/views/posts/edit.html.erb -->

<%= form_for @post do |f| %>

  <%= f.date_field :date %>
  <%= f.text_area :rationale %>

  <%= f.submit 'Save' %>

<% end %>

If you're thinking this is a bad way of implementing the edit feature, you're right. We'll come back later and refactor the code. Remember that right now our aim is to get the test passing.

If you run rspec now, the error message is that the action update cannot be found in PostsController. So, let's build our edit action.

Go to posts_controller.rb and put it in the following code:

# app/controllers/posts_controll.erb

def update
  @post.update(post_params)
end

Now, rspec says it cannot find the template, so let's redirect update to the right page.

# app/controllers/posts_controll.erb

def update
  @post.update(post_params)
  redirect_to @post, notice: 'Your post was updated successfully'
end

Now, rspec works fine.

Since everything is working, let's refactor our code. Go to the update action, and refactor the code to utilize an if-statement.

# app/controllers/posts_controll.erb

def update
  if @post.update(post_params)
    redirect_to @post, notice: 'Your post was updated successfully'
  else
    render :edit
  end
end

Next, let's go to edit.html.erb. Since it looks exactly like our new form, there is duplicate code. So, we'll create a partial called _form.html.erb with the following code:

<!-- app/views/posts/_form.html.erb -->

<%= form_for @post do |f| %>

  <%= f.date_field :date %>
  <%= f.text_area :rationale %>

  <%= f.submit 'Save' %>

<% end %>

Now, remove all the code in both the edit.html.erb and new.html.erb files and replace it with this code:

<%= render 'form' %>

We can also add an <h1> tag for page headings to designate which one is the Edit and New pages, respectively.

Now run rspec to check if everything is working, and you'll see that it's working properly.

Now we can check it in the browser.

medium

If I make changes to a post and click Save, it takes me to the show page. If I come back to posts page, I can see the updated changes.

Resources