- Read Tutorial
- Watch Guide Video
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:
- Create a
post
- Visit the
posts
index
page - Click on the
edit
button - 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:
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.
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.