Easels App with Test Driven Development: Part 4

Free Ruby on Rails Tutorial

Learn how to refactor your code and streamline your test code in Ruby on Rails with this in-depth tutorial, guiding you to make your code more dynamic and reusable through key techniques like separating shared functionality, utilizing private methods, and more.

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:

Refactoring the CompletionsController, Streamlining the Test Code, Ensuring the User Must Sign in to Create a To-do, Verifying the Successful Test Results in a Browser

Exercise Overview

Although all of our feature tests are passing, the code is quite repetitive. The mantra of test driven development is red, green, refactor. When your tests initially fail, you’ll see red errors until they pass and then you’ll see green text. In this exercise you’ll go onto the next part of TDD: refactoring (cleaning up your code to make it DRY).

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

    If You Did Not Do the Previous Exercises (13A–14A)

    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 14A 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.

Refactoring the CompletionsController

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

  2. In your code editor open app > controllers > completions_controller.rb and notice how similar the create and destroy methods are. Let’s separate out the shared functionality so it can be reused between both of these methods.

  3. Below the end of the destroy method, around line 17, add the following private method for getting the to-do that the user marks as Complete/Incomplete:

    end
    
    private
    
       def get_todo
       end
    
    end
  4. Cut any of the Todo.find lines of code (either around line 4 or 11):

    @todo = Todo.find(params[:todo_id])
  5. Paste the code into the get_todo private method as shown in bold below:

    def get_todo
       @todo = Todo.find(params[:todo_id])
    end
  6. Delete the other Todo.find line (and any leftover whitespace) so the code looks like:

    def create
       @todo.completed_at = Time.current
       @todo.save!
       redirect_to root_path
    end
    
    def destroy
       @todo.completed_at = nil
       @todo.save!
       redirect_to root_path
    end
  7. Around line 3, tell the controller to run the get_todo private method any time it first loads by typing the following bold before_action:

    class CompletionsController < ApplicationController 
    
       before_action :get_todo
    
       def create
  8. Save the file and switch to Terminal.

  9. To make sure the tests still pass, type rspec in the command line. Phew!

  10. Switch back to completions_controller.rb in your code editor.

    The remaining code in the create and destroy methods looks very similar. The only difference is the first line. The code inside the create method sets the completed_at timestamp to the current time, and the same line within the delete method sets it to nothing.

  11. Under the get_todo private method and above the final end (around line 23), add the following bold code that allows us to pass in a time as an argument:

    def get_todo
       @todo = Todo.find(params[:todo_id])
    end
    
    def mark_todo(time)
       @todo.completed_at = time
       @todo.save!
    end
  12. As shown in bold, replace all the code inside the create and delete methods and tell them to run the mark_todo(time) method, noticing that the only code that differs between the methods is being passed in as an argument in place of the dynamic time:

    def create
       mark_todo(Time.current)
        redirect_to root_path
    end
    
    def destroy
       mark_todo(nil)
       redirect_to root_path
    end

    The code is a lot more dynamic and reusable now! If we add additional actions to this controller, we could reuse some of this code wherever needed.

  13. Let’s make sure the tests still work. Save the file and switch to Terminal.

  14. Type the rspec command to see that all the tests still pass.

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.

Streamlining the Test Code

  1. The other controllers already have DRY code so let’s move on to streamlining our test code. Switch back to your code editor and from spec > features open the last three test files you worked on. To keep organized, it may help you to open them in the order you created them in:

    • User_can_see_own_todos_spec.rb
    • User_marks_todo_complete_spec.rb
    • User_marks_todo_incomplete_spec.rb
  2. Switch between the three spec files to see that we reused quite a bit of code. For one, the sign in steps under the #user signs in comment are the same in all of the three files!

  3. In any of the files copy the code shown below (starting around either line 7 or 10):

    visit new_session_path
    fill_in "Email", with: "user@example.com"
    click_on "Sign In"
  4. RSpec allows us to create one or more separate files for storing custom methods that several tests share. Because the file(s) must be in a separate folder called support, in the spec folder, create a new folder and name it: support

  5. Inside the support folder, create a nested folder called features (this is because we only want the custom methods we’ll add to apply to feature tests like the ones we wrote in previous exercises).

  6. We want to create a custom method for signing in. Create a new file in the features folder called sign_in.rb. (We can name the file anything we want.)

  7. In the blank file, write the following code that any of our feature tests can access:

    module Features
    
       def sign_in_as(email)
       end
  8. Let’s break that down:

    • In order to share code between all of our feature tests, RSpec requires us to place all the code we write in this file into a module called Features. (Remember that modules are ways of keeping pieces of code separate from one another.)

    • The sign_in_as method takes an argument called email that allows us to pass in the email address of the user who is signing in.

  9. Paste the code you copied earlier inside the sign_in_as(email) method as shown:

    def sign_in_as(email)
       visit new_session_path
       fill_in "Email", with: "user@example.com"
       click_on "Sign In"
    end
  10. To make the pasted code dynamic, all we need is to change the dummy email address string in the fill_in method around line 5. Make the edit as shown in bold:

    fill_in "Email", with: email
  11. Save the file.

  12. Switch to user_can_see_own_todos_spec.rb (which should be the first test tab).

  13. Under the #user signs in comment around line 9, call the sign_in_as method and remove the previous code:

    #user signs in
    sign_in_as("user@example.com")

    NOTE: We are passing in the dummy email address we used earlier as an argument. Additionally, we’re commenting the old code out until we make sure our test passes.

  14. Save the file and switch to Terminal.

  15. Type rspec in the command line.

    As indicated by one F and four periods, one of our five tests failed. Even though the user was able to successfully see their own to-dos the last time we ran the test suite, they are no longer able to sign in as user@example.com because we haven’t defined the `sign_in_as’ method.

  16. In order to load the shared code in sign_in.rb, we need to make some modifications. Switch back to your code editor.

  17. Open the file that is loaded into every test: spec > rails_helper.rb

  18. Around line 23, UNcomment the following code:

    Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

    As you can see in the explanatory comment above this code, this line auto-requires all files in the support directory. It’s telling RSpec to access anything inside the support folder, then any subfolder (such as our features folder), then for any individual files that end in .rb (the asterisks are wildcards that mean “any”).

  19. Around line 53, add the following bold code:

    config.infer_spec_type_from_file_location!
    
    config.include Features, type: :feature

    This include statement is necessary to load the Feature module where we are storing the code shared between our :feature tests. This powerful statement extends our test suite to include any and all of the Feature module’s methods, no matter how many we add later.

  20. Save the file and switch to Terminal.

  21. Type rspec in the command line.

    We’re back in the green! Because all of our tests are passing, we know that the sign_in_as method works.

  22. Back in user_can_see_own_todos_spec.rb, delete the comments under #user signs in so the code looks as follows:

    #user signs in
    sign_in_as("user@example.com")
    
    #user creates a to-do via a form
  23. Save the file.

  24. In user_marks_todo_complete_spec.rb, replace the three lines under #user signs in with:

    #user signs in
    sign_in_as("user@example.com")
  25. Save the file.

  26. Make the same modification in user_marks_todo_incomplete_spec.rb (and make sure to save when done!):

  27. Switch to Terminal and type rspec to run the test suite.

    All the tests pass, proving that we’re able to use the shared sign_in_as method in any of our feature tests. Let’s DRY up our test code even more.

  28. Return to user_can_see_own_todos_spec.rb (the first test tab) in your code editor.

  29. In order to test this feature and all the subsequent ones, the user needs to be able to create a new to-do and save it to the database. Let’s combine all this code into a single shared method. From around line 13–17, copy all the code shown below:

    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"

    We want to paste this code into a new shared method, which we could either put in a new file inside the support > features folder or inside sign_in.rb (it doesn’t matter what we choose).

  30. For the sake of convenience let’s keep our code in one file. Because our file will have more than just a sign in method, rename sign_in.rb to helper_methods.rb

    TIP: If you are using Sublime Text, you can CTRL–click or Right–click on the filename and choose Rename. At the bottom enter helper_methods.rb and hit Return.

  31. Switch to helper_methods.rb and add the following method around line 9 before the final end:

    def create_todo(title)
    end
  32. Inside the method paste in the copied code. Remove the comments and whitespace so it looks as shown below:

    def create_todo(title)
       click_on "Add a New To-Do"
       fill_in "Title", with: "paint house"
       click_on "Add To-Do"
    end
  33. To make the pasted code dynamic, make the following bold edit around line 11:

    fill_in "Title", with: title
  34. Save the file.

  35. Switch back to the first test tab: user_can_see_own_todos_spec.rb

  36. Under the #user creates a to-do via a form comment around line 12, call the method (passing in the string), and temporarily comment out the pre-existing code:

    #user creates a to-do via a form
    create_todo("paint house")
  37. Save the file and switch to Terminal.

  38. Type rspec in the command line to see all five tests pass!

  39. Back in user_can_see_own_todos_spec.rb, delete the code you just commented out so you have:

    #user creates a to-do via a form
    create_todo("paint house")
    
    #verify that the page has the signed in user's to-do, but not the to-do created in the background
  40. Save the file.

  41. Make the same modifications in the other two files (the complete and incomplete specs), replacing the code from around lines 11–15. Save each file when done.

  42. The user also had create a to-do in our second test: user_creates_todo_spec.rb Open that file so we can refactor it.

  43. Call the create_todo method, replacing the code from around lines 10–14:

    #user creates a to-do via a form
    create_todo("paint house")
    
    #verify that the page has our to-do
  44. Save the file and switch to Terminal.

  45. Type rspec in the command line.

    We’ve refactored all the repetitive code and all five tests are still passing! An advantage of sharing code in a shared module is the greater ease of making any necessary changes to these methods later.

Ensuring the User Must Sign in to Create a To-Do

We’ve followed the test driven design mantra of red, green, refactor from start to finish. But come to think of it, we have one more thing to do. At this juncture any old schmuck can create a to-do. Let’s add a method that ensures a user must be signed in before they can add their tasks to our site.

  1. We now need to define this method in our ApplicationController so it can be used throughout the app. Open: app > controllers > application_controller.rb

  2. Around line 12, before the final end, add the following bold method that redirects a user who isn’t signed in to the new_session_path where they can log into the app:

    def set_current_email
       @current_email = session[:current_email]
    end
    
    def authenticate!
       redirect_to new_session_path unless @current_email
    end

    Just like the Devise gem for user authentication, the method we wrote checks the controller to make sure the user is signed in. Remember that we are using the current_email method (around line 6) to log the user in. !current_email means that the user is not signed in.

  3. Save the file.

  4. To require the user to be signed in before creating a to-do, we need to call the method in the TodosController using a before_action. Switch to todos_controller.rb.

  5. Around line 3, tell the controller to run the authenticate method any time it first loads by adding the bold before_action:

    class TodosController < ApplicationController
    
       before_action :authenticate!
    
       def index
  6. To keep everything secure, we should go ahead and make the same change to app > controllers > completions_controller.rb as well:

    class CompletionsController < ApplicationController
      before_action :authenticate!
      before_action :get_todo
  7. Every time you make a change to your code, it’s necessary to check that the change did not break a different part of the application. Save the file and switch to Terminal.

  8. Type rspec in the command line.

    We have two failures–let’s resolve them one at a time. The first error message shows that the user can no longer create a to-do successfully. Capybara tried to create a to-do of "paint house" but it couldn’t find the "Add a New To-Do" button.

  9. Switch to user_creates_todo_spec.rb in your code editor so we can see why it fails.

    Yikes, we don’t have any step in which the user signs into the app! When we created this test we assumed the user did not have to be signed in. Let’s update our test to take our new authenticate method into account.

  10. Around line 9, add the following bold sign in step (which should work because the user will get redirected to the sign in page after they visit the root_path):

    visit root_path
    
    #user signs in
    sign_in_as("user@example.com")
    
    #user creates a to-do via a form
  11. Save the file and switch to Terminal.

  12. Type rspec in the command line.

    Progress—we only have one failure! Now we need to make sure that the user can successfully visit the homepage and see the proper CSS with text of 'To-Dos'.

    If you guessed that this is happening because the user needs to be signed in to see the homepage, you’re correct! To fix this issue we can either change where the route goes to or we can move the h1 element to the main page of our application so that a visitor will always see it. The second option is easier so let’s do that.

  13. Back in your code editor, open the index page: app > views > todos > index.html.erb

  14. On the first line cut the <h1>To-Dos</h1> code and any remaining whitespace.

  15. Save the file.

  16. Open the global view template: app > views > layouts > application.html.erb

  17. In order for the heading to be a permanent, static part of the site, paste it within the body tag but before the yield tag (around line 10), as shown in bold:

    <body>
       <h1>To-Dos</h1>
    
    <%= yield %>
  18. Save the file.

  19. In Terminal, type rspec in the command line.

    Great, everything passes and now all our five tests are much cleaner!

Verifying the Successful Test Results in a Browser

Now that we’ve cleaned up our test code and everything is working as we intended, let’s start the server so we can see all the hard work we did. Let’s play around with our app in a browser!

  1. Type rails server in the command line to start the server.

  2. Open a web browser and go to the sign in page at: localhost:,000/sessions/new

    Great, we can see the To-Dos headline we just moved to our app’s main page! We’ve now gone full circle with test driven development. Because our browser simulator, Capybara, spared us the tedium of constantly testing in a browser, once we view the site in an actual browser, the features on our site are guaranteed to work.

  3. Now that we’re requiring users to log in using an email address, let’s do so. In the Email field, enter test@example.com (or some other email address).

  4. Hit Sign In to authenticate yourself.

  5. Now we can add a task to our list. Click Add a New To-Do.

  6. In the Title field, enter the name of your task such as buy milk.

  7. Hit Add To-Do to save the to-do to the database.

  8. Continue to play around with the app by doing one or more of the following:

    • You can add more to-dos such as clean the bathroom, walk the dog, etc.
    • You can hit Complete and then Incomplete to see the functionality.

    While the app works, nothing is styled. We’ll keep the simple look but add a few basic styles to make for a better user experience. For one, we really need a way to distinguish the complete tasks from the incomplete tasks. We could apply any styling we want. Let’s “cross off” the completed to-dos by adding a strike-through.

  9. Leave the page open in your browser so we can reload it when we come back.

  10. Switch back to your code editor.

  11. In the app > assets > stylesheets folder, create a new file called: styles.css

  12. We prepared a snippet for you to save time. On the Desktop navigate to Class Files > yourname-Rails Bootcamp and open easels-styles.css in your code editor.

  13. Select all the code and copy it.

  14. Close the file. If you aren’t back in styles.css, switch to it.

  15. Paste the code into the empty style sheet. Feel free to examine these simple styles.

  16. Save styles.css.

  17. Switch back to the browser where the Easels app should still be open.

  18. Reload the page and play around with the app.

    That’s it! Admire your creation that you built using test-driven development!

  19. When you’re done, back in Terminal, stop the server by hitting CTRL–C.

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