Easels App with Test Driven Development: Part 2

Free Ruby on Rails Tutorial

Delve into the third tutorial on Ruby on Rails, where you'll be introduced to the concepts of setting up tests that build on previous features, with the key focus of ensuring that the user only views their own to-do lists.

This exercise is excerpted from Noble Desktop’s past web development training materials. Noble Desktop now teaches JavaScript and the MERN Stack in our Full Stack Development Certificate. To learn current skills in web development, check out our coding bootcamps in NYC and live online.

Topics Covered in This Ruby on Rails Tutorial:

Setting up a Test That Builds on Previous Features, Test #3: Ensuring the User Only Sees Their Own To-dos

Exercise Overview

In the previous exercise you created the Easels task management app, building new functionality using test driven development. Now that the user can see the homepage and add a to-do task to the list, we need to run another test that makes sure that a user can only see their respective to-do.

  1. If you completed the previous exercises, you can skip the following sidebar. We recommend you finish the previous exercise (13A) before starting this one. If you haven’t finished it, do the following sidebar.

    If You Did Not Do the Previous Exercise (13A)

    1. Close any files you may have open.
    2. Open the Finder and navigate to Class Files > yourname-Rails Class
    3. Open Terminal.
    4. Type cd and a single space (do NOT press Return yet).
    5. Drag the yourname-Rails Class folder from the Finder to the Terminal window and press ENTER.
    6. Run rm -rf easels to delete your copy of the easels site.
    7. Run Git clone https://bitbucket.org/Noble Desktop/easels.Git to copy the easels Git repository.
    8. Type cd easels to enter the new directory.
    9. Type Git checkout 13A to bring the site up to the end of the previous exercise.
    10. Run bundle to install any necessary gems.
    11. Run yarn install—check-files to install JavaScript dependencies.

Setting up a Test That Builds on Previous Features

  1. Make sure Terminal is open. If you aren’t in the easels directory, type: cd easels

  2. Run rails g rspec:feature user_can_see_own_todos

  3. In the new file (spec > features > user_can_see_own_todos_spec), update as shown:

    require "rails_helper"
    
    RSpec.feature "User Can See Own Todos", type: :feature do
       scenario "successfully" do
       end
    end
  4. What steps does the user need to take to see their own to-dos? For one, they are going to need to create a new to-do, save it to the database, and see some CSS. That sounds familiar! To save time, let’s copy some steps from the previous test. Open: spec > features > user_creates_todo_spec.rb

  5. Around lines 9–17, copy the last three comments and the code under them:

    • #user creates a to-do via a form
    • #user saves the to-do to the database
    • #verify that the page has our to-do
  6. Back in user_can_see_own_todos_spec.rb, paste the code into the empty scenario:

    scenario "successfully" do
    
       #user creates a to-do via a form
       click_on "Add a New To-Do"
       fill_in "Title", with: "paint house"
    
       #user saves the to-do to the database
       click_on "Add To-Do"
    
       #verify that the page has our to-do
       expect(page).to have_css ".todos li", text: "paint house"
    
    end
  7. To ensure the user only sees their own to-do, we need the database to associate tasks with users. Let’s create a very basic sign in system based on the user’s email. Above the pasted code, type the two comments and code with Capybara syntax:

    #create different user's to-do in background
    Todo.create(title: "buy milk", email: "somebody@example.com")
    
    #user signs in
    visit root_path
    fill_in "Email", with: "user@example.com"
    click_on "Sign In"
    
    #user creates a to-do via a form

    Essentially we’re creating two to-dos at the same time. While our user is adding their "paint house" task, a dummy "buy milk" to-do is created in the background. We’re going to make sure we only see one of them—the to-do which belongs to the signed in user.

  8. To ensure we see the logged in user’s to-do but not the dummy task, revise the final comment and add some more code under it, as indicated in bold:

    #verify that the page has the signed in user's to-do, but not the to-do created in the background
    expect(page).to have_css ".todos li", text: "paint house"
    expect(page).not_to have_css ".todos li", text: "buy milk"
  9. Save the file and switch to Terminal.

Full-Stack Web Development Certificate: Live & Hands-on, In NYC or Online, 0% Financing, 1-on-1 Mentoring, Free Retake, Job Prep. Named a Top Bootcamp by Forbes, Fortune, & Time Out. Noble Desktop. Learn More.

Test #3: Ensuring the User Only Sees Their Own To-Dos

  1. Run the new feature test by entering the following (in Sublime Text you can copy the filename by CTRL–clicking or Right–clicking on it and choosing Copy Name):

    rspec spec/features/user_can_see_own_todos_spec.rb

    The test fails the first step: creating the dummy to-do of "buy milk". Rails doesn’t know anything about the Todo model’s 'email' attribute.

  2. Back in your code editor, open db > schema.rb. This file shows the fields that are currently in our database.

  3. It’s important not to edit this file, as we do that by running migrations. Around line 17, notice that we only have a "title" field.

  4. In Terminal run a migration to add an email field (a string value) to each to-do:

    rails g migration AddEmailToTodo email:string
  5. To update the database, type rails db:migrate in the command line.

    NOTE: Feel free to look back at schema.rb to see that a new "email" field was added. Now we should have enough to get around this test!

  6. To run the test again, enter the following command: bundle exec rspec spec/features/user_can_see_own_todos_spec.rb (Remember that you can hit the Up Arrow key. We won’t remind you again.)

    This failure message does not mention the "buy milk" dummy to-do, which means we were able to create it in the background! Now our browser simulator Capybara is trying to log in. So why can’t it find the "Email" field? Let’s go sleuthing.

  7. In your code editor, pull up the current test (user_can_see_own_todos_spec.rb).

    Under the #user signs in comment (around line 9), notice that the user is going to the root_path to sign in.

  8. Let’s see what we have on our homepage. Open: app > views > todos > index.html.erb

    Hmm, all we have is an unordered list that shows all the to-dos, followed by the link that adds new to-dos to the database. This page doesn’t have a login form!

    While we could add the login form to this page, it doesn’t make sense. Now that we’re associating tasks with logged in users, a user should log in before they can create to-dos. Let’s reconfigure our routes instead.

  9. Open config > routes.rb.

    In order to get around our current test failing, we’re going to create a sessions controller. After some configuration it will handle the sign in form we’ll create.

  10. Add another set of resourceful routes by adding the bold code (around line 5):

    root to: "todos#index"
    resources :todos
    resources :sessions
  11. Save the file and switch to Terminal.

  12. To see which new routes are now available, type: rails routes

    In addition to the todos paths, we now have paths for sessions! The new_session path (using the new action) adds a new session, routing the user to the sign in page. Come to think of it, we should make the user go to this path instead of the root path. Let’s revise our test.

  13. Switch back to user_can_see_own_todos_spec.rb in your code editor.

  14. Around line 10, modify the test as shown in bold below:

    #user signs in
    visit new_session_path
    fill_in "Email", with: "user@example.com"
  15. Save the file and switch to Terminal.

  16. Run the test: rspec spec/features/user_can_see_own_todos_spec.rb

    As you may have expected, we get a RoutingError similar to what we saw in a previous test. The SessionsController doesn’t exist yet. Let’s initialize it so we can visit the new_session_path.

  17. Run rails g controller sessions.

  18. Run the test: rspec spec/features/user_can_see_own_todos_spec.rb

    Just like anything else in programming, test driven development gets easier with practice. You may recognize this kind of error from previous tests. The last time a controller couldn’t find an action, we had to define it in our controller file.

  19. In sessions_controller.rb create the new action as follows:

    class SessionsController < ApplicationController
    
       def new
       end
    
    end
  20. Save the file and switch to Terminal.

  21. Run the test: bundle exec rspec spec/features/user_can_see_own_todos_spec.rb

    It looks like we’ve previously seen this kind of failure as well. Now that our controller is working, we have a MissingTemplate. Let’s make a corresponding view.

  22. In the app > views > sessions folder, create a new file called: new.html.erb

  23. We know from experience that if we run the test now, the next failure will be for the form. Create the form by adding the following code to the empty view:

    <%= form_with url: '/sessions', method: :post do |f| %>
       <%= f.label :email %>
       <%= f.text_field :email %>
       <%= f.submit "Sign In" %>
    <% end %>
  24. Let’s break down that first line:

    • We are using form_with url: instead of form_with model:. This is because we don’t have a session model (nor do we plan to create one at this time).
    • We have not declared a @session instance variable because we don’t have anywhere in the database to store the session information. We don’t need it for this simple app. Instead we’ll be temporarily holding onto the session info in the browser.
  25. Save the file and switch to Terminal.

  26. Run the test: bundle exec rspec spec/features/user_can_see_own_todos_spec.rb

    Making progress! Now it’s not able to click on "Sign In" because the SessionsController has no create action.

  27. In sessions_controller.rb modify the code to add the create action as follows:

    def new
    end
    
    def create
    end
  28. Save the file and switch to Terminal.

  29. Run the test: bundle exec rspec spec/features/user_can_see_own_todos_spec.rb

    It’s trying to use the create action but we have a missing template in sessions/create. Remember that the create action doesn’t have a page that displays. Let’s add a redirect in our controller.

  30. In sessions_controller.rb add the following bold code:

    def create
       session[:current_email] = params[:email]
       redirect_to root_path and return
    end

    Like we did to the TodosController in the previous exercise, the quick and easy way to get around the failure is to redirect to the homepage. However, this time we need to set our session equal to something so that we can hold the user’s session in the browser. The first line we added uses a special Rails feature called session object. session is a special variable in Rails that stores values unique to each visitor to the site. In this case, we set the :current_email key within the session object equal to the user typed into the new :session form.

  31. Save the file and switch to Terminal.

  32. Run the test: bundle exec rspec spec/features/user_can_see_own_todos_spec.rb

    Finally it was able to successfully visit the new_session_path! Because we’re reusing so many steps which worked in our last feature test, our test failed on the very final step. The page should not have a list item with the ".todos li" CSS and "buy milk" text, but it’s there. Our signed in user’s "paint house" task is here too. We need to restrict the to-dos that we display on our homepage.

  33. Switch back to your code editor and open: app > controllers > todos_controller.rb

  34. The controller is currently pulling out all of the to-dos in the database and displaying them on the homepage. We want to redefine the @todos instance variable so it is equal to the signed in user’s to-dos. There are a few ways to do that—ideally, we’d create a full User model and authenticate it first. For now, we’ll do what is easiest, in keeping with our TDD best practices. Remove the existing line and replace it:

    def index
       @todos = Todo.where(email: @current_email)
    end
  35. Save the file.

  36. We now need to define the @current_email instance variable in our ApplicationController so it can be used throughout the app. Open: app > controllers > application_controller.rb

  37. Add the bold code:

    class ApplicationController < ActionController::Base
      before_action :set_current_email
    
      private
        def set_current_email
          @current_email = session[:current_email]
        end
    end

    We’re defining a @current_email instance variable. We are setting it equal to session[:current_email], as we previously used that code in our SessionsController. Feel free to go back to sessions_controller.rb to see that we defined it to be equal to the email address the user added in the new session form.

    NOTE: You can also go back to todos_controller.rb to refresh yourself on how to-dos are associated with the current_email.

  38. Save the file and switch to Terminal.

  39. Run the test: bundle exec rspec spec/features/user_can_see_own_todos_spec.rb

    Our test is still failing, but a step earlier. This time it couldn’t find the first to-do, the signed in user’s "paint house" task. It looks like the to-dos are still not being associated with an email address. Let’s revise the code that successfully saved the to-dos to the database before we added user logins.

  40. Go to todos_controller.rb in your code editor.

  41. In the create method, make the following bold code edits (around line 16):

    #@todo = Todo.new(todo_params)
    @todo.email = @current_email
    @todo.save

    We previously were creating a task with a title attribute but no email attribute. This updated code creates each to-do so it is associated with an email (the current_email that the user signed in with).

    A few additional notes. First of all, we replaced Todo.create with Todo.new. Create creates the new Todo and saves it all in one step. But, now we need to modify it before saving, so we use new instead.

    We could have just added :email to the list of permitted parameters below, in the todo_params method. We didn’t do that because that would allow troublemakers to push any email address through the form and create todos for other people. (This is exactly the scenario strong parameters exist to defend against.) Instead we are limiting it to the email they are authenticated with, by we explicitly setting it in the controller.

  42. Save the file and switch to Terminal.

  43. Run the test: bundle exec rspec spec/features/user_can_see_own_todos_spec.rb

    Woo hoo, it passed! Now a user can sign in, add a new to-do, and save it to the database. Now that the database associates tasks with users, only the user’s own "paint house" to-do is displaying, even though a random user’s "buy milk" to-do was created in the background. Very awesome!

  44. Run all the tests using rake to see that all three pass.

    Our app is working but it is a little brittle. If we go to the root_path of our application, we may not necessarily see any tasks because it requires the user to be signed in. We can also create tasks without an email address—that’s no good! We’ll change that in a later exercise.

  45. We will continue with this app in the next exercise. To minimize confusion, leave the easels folder open in your code editor but close all the individual files that are open.

    In the next exercise, we’ll create two more tests: one that ensures the user can mark a to-do complete, and one that makes sure they can mark a task incomplete.

Noble Desktop Publishing Team

The Noble Desktop Publishing Team includes writers, editors, instructors, and industry experts who collaborate to publish up-to-date content on today's top skills and software. From career guides to software tutorials to introductory video courses, Noble aims to produce relevant learning resources for people interested in coding, design, data, marketing, and other in-demand professions.

More articles by Noble Desktop Publishing Team

How to Learn Coding

Master Coding with Hands-on Training. Learning How to Code in JavaScript, Python, and Other Popular Languages Can Pave the Way to a Job in Tech Such As Web Development, Data Science & Analytics, or Software Engineering.

Yelp Facebook LinkedIn YouTube Twitter Instagram