- Read Tutorial
Let's keep going with building our Topic
feature. Since we have a working index
view that links to a show
view it seems like a natural fit to build out the show
action functionality. This will eventually be where users will be able to see posts for a specific topic, such as the Baseball
topic show page having posts such as Houston Astros Batting
.
We're still on the same branch since we're still working on the topic feature, so we can get started.
Start off by updating the topic feature spec to see if it can reach the show
page. We're going to create a new describe
block and put a base case test and some pending tests:
# spec/features/topic_spec.rb describe 'show' do it 'can be reached successfully' do visit topic_path(@topic) expect(page.status_code).to eq(200) end xit 'should display the topic title' do end xit 'should have a url that matches the custom url slug' do end end
Running this spec will give us a failure saying The action 'show' could not be found for TopicsController
. Let's get this passing by creating a controller show
action and creating the template file:
# app/controllers/topics_controller.rb def show end
And create the template file:
touch app/views/topics/show.html.erb
Now if we run rspec spec/features/topic_spec.rb
the test will pass. With our base system setup let's fill out the next spec:
# spec/features/topic_spec.rb it 'should display the topic title' do visit topic_path(@topic) expect(page).to have_css('h1', text: 'Sports') end
Notice here that I'm leveraging the Capybara have_css
matcher that not only looks for the content, but also ensure it is inside of an HTML <h1>
header tag. Running this spec will fail, to get it working let's implement the minimum amount of code possible.
First, we need to update the show
method so it can pass in the topic values to the view:
# app/controllers/topics_controller.rb def show @topic = Topic.find(params[:id]) end
Next, update the show
template:
<!-- app/views/topics/show.html.erb --> <h1><%= @topic.title %></h1>
Now if you run the specs they're all passing... not quite. In fact now we're getting two failures, what gives? Let's look at the error:
ActiveRecord::RecordNotFound: Couldn't find Topic with 'id'=sports
Ok, I screwed up (I'd love to say it was on purpose, but I really did forget something important), remember how we're using the friendly_id
gem? The normal rails query of Topic.find(params[id]
won't work because it's looking for an ID but we're passing a URL like localhost:3000/sports
, which actually has an ID that's an integer. Thankfully the Friendly ID
gem has a handy method we can use in our controllers to fix this. Update the controller and pass in the friendly
method:
# app/controllers/topics_controller.rb def show @topic = Topic.friendly.find(params[:id]) end
Do you see how using a BDD approach is very helpful? Failing tests can quickly tell us when we forget something on the implementation side like it just did. Now let's flesh out the last spec:
# spec/features/topic_spec.rb it 'should have a url that matches the custom url slug' do visit topic_path(@topic) expect(current_path).to have_content('sports') end
This is a little backwards since we typically want our tests to fail first (following the Red, Green, Refactor pattern), however this test is going to pass right away since the functionality was needed to get the other tests passing. Don't worry about it though, processes don't always have the same flow 100% of the time, even professional Rails developers. I put this spec in the test suite, even when I knew it was going to pass, to ensure that if something were to ever happen to the custom URL functionality, this spec should tell us what happened. I typically like having base case tests throughout my test suite so I always have the ability to isolate them in case something goes wrong (such as all of the tests failing for an unknown reason out of the blue... it happens... it happened to me today on DevCamp actually).
Now that our tests are passing, let's refactor some code. Starting with the tests themselves, do you notice some duplication in our show
block? Let's change:
# spec/features/topic_spec.rb describe 'show' do it 'can be reached successfully' do visit topic_path(@topic) expect(page.status_code).to eq(200) end it 'should display the topic title' do visit topic_path(@topic) expect(page).to have_css('h1', text: 'Sports') end it 'should have a url that matches the custom url slug' do visit topic_path(@topic) expect(current_path).to have_content('sports') end end
To:
# spec/features/topic_spec.rb describe 'show' do before do visit topic_path(@topic) end it 'can be reached successfully' do expect(page.status_code).to eq(200) end it 'should display the topic title' do expect(page).to have_css('h1', text: 'Sports') end it 'should have a url that matches the custom url slug' do expect(current_path).to have_content('sports') end end
See how the visit topic_path(@topic)
call was inside every spec? That's always a sign that it should be placed into a before
block.
Now open up the controller. Even though it's not an issue right now, the query for a topic is going to be used by multiple methods in the future, so let's setup a before_action
that sets the @topic
instance variable.
# app/controllers/topics_controller.rb class TopicsController < ApplicationController before_action :set_topic, only: [:show] def index @topics = Topic.all end def show end private def set_topic @topic = Topic.friendly.find(params[:id]) end end
This refactor will prevent duplicate code from being written in the future for the Topic
query, all we'll need to do to give access to the @topic
instance variable is add the method name, as a symbol, to the array like we did with [:show]
. Also notice that I declared that the set_topic
method is private
. This is because we don't want any other part of the application calling this method, only the controller methods we give permission to do so.
Running the specs again will show that our refactor worked and all of the tests are still passing. Let's startup the server and navigate to localhost:3000
to ensure that our tests aren't lying (it's always a good practice to test out a feature in a browser since it's always possible that the tests could be missing edge cases). However in this case it looks like everything is working properly, if we click on one of the topics we're taken to the topic's show page and the custom URL is also working, nice work!
Make sure to make your git commit and in the next lesson we'll build out our form so that topics can be created and edited in the browser.