- Read Tutorial
In this lesson we're going to walk through the steps for building in the feature where a user will be able to navigate to a specific Topic
page and be shown all of the posts
nested inside of that topic. As a real world example, if you go to the news section on Reddit you will see all of the posts that are under the category of news
.
Now that you know what we're going to build, let's get started by letting our topics
factory know that it should have cases where it has multiple posts associated with it, we can do this by updating the factory like so:
# spec/factories/topics.rb FactoryGirl.define do factory :topic do title 'Sports' factory :topic_with_posts do transient do posts_count 2 end after(:create) do |topic, evaluator| create_list(:post, evaluator.posts_count, topic: topic) end end end factory :second_topic, class: 'Topic' do title 'Coding' end end
This update let's the factory know that it may have a case where it has multiple posts associated with it, which, for the post_spec
tests will be all of them. Opening up our post_spec
let's add in the test cases that we want to build for this feature and include the first live spec:
# spec/features/post_spec.rb require 'rails_helper' describe 'post' do before do @topic = FactoryGirl.create(:topic_with_posts) end describe 'nested route' do before do visit topic_posts_path(topic_id: @topic) end it 'has an index page properly nested under a topic' do expect(page.status_code).to eq(200) end it 'renders multiple posts' do expect(@topic.posts.count).to eq(2) end xit 'has the post title' do end xit 'has the post description' do end xit 'has the post user name' do end xit 'has post links that point to post show pages' do end end end
Here we're going to check on basic features associated with an index page, such as: making sure multiple posts are returned, ensuring the title is rendered, ensuring the description is shown, listing the post user name, and making sure that the post titles link to the post show page. Running rspec spec/features/post_spec.rb
will let us know that it was looking for two posts but found 0, let's get that working. First update the index
action in the PostsController
. One key to working with nesting is knowing that you need to always call the parent element, so for this example our index
action needs to know what Topic
we're working with, we can find this by looking through the params and storing the topic_id
in a variable and then using that to query all of the posts that belong to it:
# app/controllers/topics/posts_controller.rb class Topics::PostsController < ApplicationController def index topic = Topic.friendly.find(params[:topic_id]) @posts = topic.posts end end
Now in our view we can print out those values:
# app/views/topics/posts/index.html.erb <%= @posts.each do |post| %> <%= post %> <% end %>
Running the specs now will show that they're all passing, let's make a small refactor, we know that we're going to need that Topic
query for other methods in the post
controller, so let's move it into its own private method, so the controller should now look like this:
# app/controllers/topics/posts_controller.rb class Topics::PostsController < ApplicationController before_action :set_topic def index @posts = @topic.posts end private def set_topic @topic = Topic.friendly.find(params[:topic_id]) end end
Our tests are still passing, perfect. Moving onto the next spec, let's add in the expectation:
# spec/features/post_spec.rb it 'has the post title' do expect(page).to have_content('My Great Post') end
This test passes already because our last implementation already integrated this, however I thought it was important to include since it would be possible for the last test to pass (since it's only looking for the count), and this one to fail, the fact it's passing means we're good to go. The next spec is looking for the post description, let's add in this expectation:
# spec/features/post_spec.rb it 'has the post description' do expect(page).to have_content('Amazing content') end
This expectation actually passes too, but why? This is where you should sit back and think that maybe the tests or previous implementation weren't setup properly since we're not following the true TDD/BDD process, too many of our tests are passing. In this case the reason is because our view code implementation, if you look at the view we're actually printing out the entire object to the page, so the title, id, content, etc. are all showing up. Obviously this isn't the implementation we want, so let's clean up the view:
# app/views/topics/posts/index.html.erb <%= @posts.each do |post| %> <%= post.title %> <%= post.content %> <% end %>
The tests are still passing, but they're passing for the right reasons now. It's good to do a sanity test, don't simply rely on the test results themselves, make sure you're also checking the behavior in the browser to ensure the implementation is matching the goal. Now that we're back on track let's implement the next spec:
# spec/features/post_spec.rb it 'has the post user name' do expect(page).to have_content(@topic.posts.last.user.username) end
This spec is a little bit of an anti-pattern since it violates the Law of Demeter, which would say that we're using too much method chaining, our topic is listing its posts (that's fine), it's then calling a single post, then it's calling the user for that post, and then it's calling the username for that user. For right now we'll keep this, but it would be a good piece to come back and refactor later on. Running this spec will fail since our view isn't rendering any information about that the posts' user, so let's implement that:
# app/views/topics/posts/index.html.erb <%= @posts.each do |post| %> <%= post.title %> <%= post.content %> <%= post.user.username %> <% end %>
This works, perfect. Now let's create the final expectation:
# spec/features/post_spec.rb it 'has post links that point to post show pages' do expect(page).to have_link(@topic.posts.last.title, href: topic_post_path(topic_id: @topic, id: @topic.posts.last)) end
This is checking the page to see if the posts are linking to their respective show pages, running these specs will give an error since it can't find the link, let's update that in the view code:
# app/views/topics/posts/index.html.erb <%= @posts.each do |post| %> <%= link_to post.title, topic_post_path(topic_id: @topic, id: post) %> <%= post.content %> <%= post.user.username %> <% end %>
Notice how we had to call the @topic
value, whenever you're using nesting you need to ensure that you're always referencing the parent resource. Now if you run rspec
you'll see all of the tests are passing, nice work! If you navigate through the site you'll see everything is working properly.
*Side note: * If you're having issues in the browser, make sure that the posts that you created in the console have a topic and user associated with them or you'll get an error, we'll be adding a validation for this later on.