Explore the specifics of task management features in Ruby on Rails with this tutorial. Gain a comprehensive understanding of user task features such as marking tasks as complete or incomplete, generating new features, creating, saving and deleting tasks, and more.
Key Insights
- The tutorial outlines a step-by-step process of implementing task management features in a Ruby on Rails application. This includes generating new features, creating and saving tasks to the database, and marking tasks as complete or incomplete.
- Certain actions such as 'Complete' and 'Incomplete' require routes to function appropriately. The tutorial details how to create these routes and integrate them into the application.
- The tutorial presents the process of creating a CompletionsController and defining 'create' and 'destroy' methods for marking tasks as complete and incomplete respectively.
- The tutorial also demonstrates how to run tests for each new feature or method implemented. This process ensures that each function works as expected.
- Refactoring code is emphasized to avoid redundancy and maintain the DRY (Don't Repeat Yourself) principle in coding.
- A practical example of a to-do list application, 'Easels', is used to demonstrate these principles throughout the tutorial.
Dive into our detailed Ruby on Rails tutorial that breaks down the process of including task management features in the Easels app, including tests to ensure users can mark their tasks as either complete or incomplete.
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:
Test #4: Ensuring the User Can Mark Their To-do Complete, Test #5: Ensuring the User Can Mark Their To-do Incomplete
Exercise Overview
Now that the user can go to the Easels homepage, sign in, save their tasks to the database, and only see their own list of to-dos, we need to add the actual task management features. The purpose of the Easels app is to provide a simple way for a user to mark to-dos in their list as either complete or incomplete. We’ll add these features one-by-one, starting with a test that ensures the user can tell the app that their task is done.
-
If you completed the previous exercises, you can skip the following sidebar. We recommend you finish the previous exercises (13A–13B) before starting this one. If you haven’t finished them, do the following sidebar.
If You Did Not Do the Previous Exercises (13A–13B)
- Close any files you may have open.
- Open the Finder and navigate to Class Files > yourname-Rails Class
- Open Terminal.
- Type
cd
and a single space (do NOT press Return yet). - Drag the yourname-Rails Class folder from the Finder to the Terminal window and press ENTER.
- Run
rm -rf easels
to delete your copy of the easels site. - Run
Git clone https://bitbucket.org/Noble Desktop/easels.Git
to copy the easels Git repository. - Type
cd easels
to enter the new directory. - Type
Git checkout 13B
to bring the site up to the end of the previous exercise. - Run
bundle
to install any necessary gems. - Run
yarn install—check-files
to install JavaScript dependencies.
Test #4: Ensuring the User Can Mark a To-Do Complete
Make sure Terminal is open. If you aren’t in the easels directory, type:
cd easels
Go to your code editor where the easels folder should be open.
-
Generate a new feature:
rails g rspec:feature user_marks_todo_complete
-
In the new file, update as shown:
require "rails_helper" RSpec.feature " User Marks Todo Complete", type: :feature do scenario "successfully" do end
Just like in the last test, our user is going to need to go through the familiar motions of signing in, creating a new to-do, and saving it to the database. Open spec > features > user_can_see_own_todos_spec.rb so we can copy some of its code.
Around lines 9–19, copy the middle three comments and the code under them (from
#user signs in
toclick_on "Add To-Do"
).-
Back in user_marks_todo_complete_spec.rb, paste the code into the empty scenario:
scenario "successfully" do #user signs in visit new_session_path fill_in "Email", with: "user@example.com" click_on "Sign In" #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" end
-
We need to add two additional steps to make sure this new feature works. First the user needs to be able to click on a button that marks the to-do as complete. Then we want to verify that the to-do is marked as complete on the page. Below the pasted code add the following:
click_on "Add To-Do" #user marks the to-do complete click_on "Complete" #verify that the to-do is marked complete expect(page).to have_css ".todos li.completed", text: "paint house" end
NOTE: There’s a number of different ways in which we can style the tasks the user marks as done. While we set up our test so the application adds a completed class to the finished to-dos, you could style them with a strike-through, remove the text, etc. In your own apps it’s entirely up to you (and your front-end developer).
Save the file and switch to Terminal.
-
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_marks_todo_complete_spec.rb
As you may have expected, all the steps we pasted from the previous test are working. The test fails at the point where the user needs to click on
"
Complete"
because Capybara can’t find it. Let’s add that button to our index view that displays the to-dos. Switch back to your code editor and open: app > views > todos > index.html.erb
-
Inside the code that loops through the to-dos and displays them, add a conditional statement that adds an Incomplete button if the task has been marked as completed (in case the user made a mistake and did not actually complete a to-do) and otherwise shows a Complete button:
<% @todos.each do |todo| %> <li> <%= todo.title %> <% if todo.completed? %> <%= button_to "Incomplete", root_path %> <% else %> <%= button_to "Complete", root_path %> <% end %> </li> <% end %>
NOTE: In order for them to function, each button must route to somewhere else in our application. We don’t know where the buttons should lead to yet so we’re temporarily routing them both to the homepage.
-
In our test we specified that finished tasks will have an additional class of completed. As shown in bold, add another conditional to the list item that adds that class only when appropriate:
<li class="<%= 'completed' if todo.completed? %>">
Save the file and switch to Terminal.
-
Enter the
rspec spec/features/user_marks_todo_complete_spec.rb
command. (Remember that you can hit the Up Arrow key to automatically enter previous commands in Terminal.)Now the user can no longer click on the
"
Add To-Do"
button that worked until we added the conditionals. The view can’t display the button because the `completed?’ method doesn’t exist. There are a few ways in which we can define this method. Let’s add a completed_at field to the Todo model by running a migration. Back in your code editor, open db > schema.rb and notice the fields we already have.
-
In Terminal run a migration to add a completed_at field (with a timestamp value) to each to-do in our database:
rails g migration AddCompletedToTodo completed_at:datetime
-
Type
rails db:migrate
in the command line.NOTE: Feel free to look back at schema.rb to see that the migration added a new
"
completed_at"
field that displays as a datetime. Now that we’ve updated the database, let’s go to the Todo model and define the completed? method there. In your code editor, open the Todo model: app > models > todo.rb
-
As shown in bold, create a completed? method that tracks whether or not the database has a completed_at field for the specific task that it’s calling:
class Todo < ActiveRecord::Base def completed? completed_at? end end
NOTE: We want to know whether or not something is being returned, so we added the question marks. This matches the usage in the conditionals on the index page.
Save the file and switch to Terminal.
-
Run the test:
rspec spec/features/user_marks_todo_complete_spec.rb
Now that the to-dos are being saved, we have a problem with the
"
Complete"
button’s routing. Because we temporarily routed it to go to the root_path, it’s trying to POST the results to the index page. We can’t do that and we don’t want to. There are many different ways to handle this. We could create a completed action in our TodosController. Let’s create a separate controller with create and destroy routes. Before we create a new controller, let’s get rid of the current error. We first need to add a route that works, so open config > routes.rb in your code editor.
-
We want to create some additional completion routes that are associated with the Todo model, so let’s create a nested route. Add the following bold code:
root to: "todos#index" resources :todos do resource :completion, only: [:create, :destroy] end resources :sessions
NOTE: We only need create and destroy routes because we only need to be able to save the completion info to the database or delete it. We created a singular resource (without pluralization), because those kinds of routes do not reference an ID. Only model objects can have :id fields. We do not have a Completion model so plural resources are not a good choice here.
Save the file and switch to Terminal.
-
Type
rails routes
in the command line.The todo_completion POST route (the second one) looks like just what the doctor ordered! Notice that :todo_id is part of the route. It is a way to let Rails know which to-do to mark as completed. We will need to utilize this soon.
Back in your code editor, switch to index.html.erb.
-
In the else statement, around line 10, make the following bold edit to the Complete button so it will take us to the respective to-do’s todo_completion_path:
<%= button_to "Complete", todo_completion_path(todo) %>
Save the file and switch to Terminal.
-
Run the test:
rspec spec/features/user_marks_todo_complete_spec.rb
Great, we know that the route works because we’re seeing a message that should be pretty familiar by now. We need to initialize a CompletionsController.
-
Create a new controller:
rails g controller completions
-
Run the test:
rspec spec/features/user_marks_todo_complete_spec.rb
Another familiar error—now we need to define the
'
create'
method in our new CompletionsController. We know from experience that we’ll need to add a redirect inside the method. Let’s do that now. -
In completions_controller.rb add the following bold method and redirect:
class CompletionsController < ApplicationController def create redirect_to root_path end end
Save the file and switch to Terminal.
-
Run the test:
rspec spec/features/user_marks_todo_complete_spec.rb
Now that the Complete button is finally functional, the test fails on the last step. It looked for the
"
paint house"
text with the CSS that includes the.completed class but could find no matches. In order to actually mark a to-do complete, we need to add more code to our create action. Switch back to completions_controller.rb in your code editor.
-
The first thing we need is to find the correct to-do. Add the following bold code:
def create @todo = Todo.find(params[:todo_id]) redirect_to root_path end
This code searches for the to-do that was passed into the controller. It’s looking inside the Todo class, trying to find the correct one by looking for its ID. We wrote this as :todo_id because that’s how it’s passed in inside of our route.
NOTE: If you need a refresher on the completions_path POST route we got the syntax from, go back to Terminal, type
rails routes
in the command line, and examine the second route’s URI Pattern. -
Once the database finds the correct to-do by its ID it needs to add a completed_at timestamp using the current time. Lastly it needs to save the results. Add more code to the create method as shown in bold:
@todo = Todo.find(params[:todo_id]) @todo.completed_at = Time.now @todo.save redirect_to root_path
Save the file and switch to Terminal.
-
Run the test:
rspec spec/features/user_marks_todo_complete_spec.rb
It passes! Now that we know we can successfully mark a to-do complete, let’s work on being able to mark them incomplete.
Run all the tests using
rspec
to see that all four pass.
Test #5: Ensuring the User Can Mark a To-Do Incomplete
Switch back to user_marks_todo_complete_spec.rb in your code editor.
-
Duplicate the file, naming it: user_marks_todo_incomplete_spec.rb
TIP: If you are using Sublime Text, you can CTRL–click or Right–click on the filename and choose Duplicate. At the bottom rename the duplicate file and hit Return.
-
We need to make a few minor modifications. Around line 3 rename the feature:
feature "user marks to-do in complete" do scenario "successfully" do
-
Next we need the user to be able to click the Incomplete button. Our app is set up so that this button only shows once the user has clicked Complete, so we still want that step in this test. Under the
click_on "Complete"
code add a new step:#user marks the to-do complete click_on "Complete" #user marks the to-do incomplete click_on "Incomplete"
-
Modify the last comment and its associated code as shown in bold:
#verify that the to-do is marked in complete expect(page).to have_css ".todos li", text: "paint house" expect(page).not_ to have_css ".todos li.completed", text: "paint house"
Save the file and switch to Terminal.
-
Run the new test using the following command. (It may be easiest to bring up the previous test’s command, press the Left Arrow key until the letter c in complete is highlighted, then type in to make it read in complete):
rspec spec/features/user_marks_todo_incomplete_spec.rb
Our first failure is a RoutingError for the
"
Incomplete"
button. Because we previously only updated the route for the Complete button, we are still using the temporary route to the root_path. Let’s find a replacement. -
Type
rails routes
in the command line.This time we want to use the todo_completion DELETE route (the third one). This is because we’re essentially deleting a completion.
Back in your code editor, switch to index.html.erb.
-
In the if statement, edit line 8 as shown in bold (replacing root_path):
<% if todo.completed? %> <%= button_to "Incomplete", todo_completion_path(todo), method: :delete %>
Save the file and switch to Terminal.
-
Run the test:
rspec spec/features/user_marks_todo_incomplete_spec.rb
Again our test fails. We can’t click on the
"
Complete"
button because no route matches the"
destroy"
:action and ActionView cannot render the template for it. Let’s add a destroy method to our CompletionsController. Just like create methods, destroy methods need a redirect because they do not have a corresponding view. Switch to completions_controller.rb in your code editor.
The destroy method will work similarly to the create method. Copy the entire create method (around lines 3–8).
Paste a copy directly below the first around line 10.
-
As shown below, make the two bold edits to rename the method and remove the completed_at timestamp:
def destroy @todo = Todo.find(params[:todo_id]) @todo.completed_at = nil @todo.save! redirect_to root_path end
Save the file and switch to Terminal.
-
Run the test:
rspec spec/features/user_marks_todo_incomplete_spec.rb
It passes! Both the Complete and Incomplete buttons are now functional.
Run all the tests using
rspec
to see that all five pass.-
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.
You may have noticed that in our haste to get the incomplete test to pass we had to reuse quite a bit of code. That’s not very DRY. In the next exercise we’ll finish the Easels app by doing a bit of refactoring before we explore the app in a browser.