Features
Until now, users once logged in, had no way of logging out of our application.
Let us implement the logout feature:
-
We can add a logout button in our navbar.
-
Clicking the logout button should terminate the session and redirect the user
to the login page.
-
From now onwards, we should keep track of who created which task. Later, we
can use this to limit access to a task to its owner and its assignee only.
-
Task detail page should also contain the name of task owner.
Technical design
We will do the following to implement the given requirements:
-
We will create a destroy action in SessionsController. We use this action
to clean up the session on the backend when the user logs out. We can map this
action to the DELETE request to the /session URL.
-
After calling the API, we will clear our browser's localStorage and reset the
headers from axios configuration.
-
Now, to add information about the task owner, we will need a new column
task_owner_id in tasks table.
-
We will add a foreign_key constraint on task_owner_id to create a
relationship between a task and its corresponding user who is the owner of the
task.
-
We will declare a has_many association with a custom name of created_tasks
in the User model and a belongs_to association for task_owner in the
Task model.
-
Inside the TasksController, we will get the currently logged-in user details
via the current_user method in the ApplicationController. We can use the
current user's created_tasks association to create a new task.
-
To send the task owner's name in JSON response, we will have to update the
show action's corresponding Jbuilder view builder.
-
To display the task creator's name in task details page, we will need to
update the Show component of Tasks to render task owner's name received
from backend along with other task details.
Sessions controller
Open app/controllers/sessions_controller.rb and add the following lines:
In the above code, along with adding a destroy action, we have also updated
the skip_before_action filter to be effective only when the request is for
create action.
Session routes
Let's modify routes.rb and add a destroy action for session:
Frontend changes
Open apis/auth.js and add following lines:
Let's open apis/axios.js and create a function to clear the default Axios
headers when the user is logged out. Add the resetAuthTokens method at the end
of the file and also update the export statement like this:
Now, let's open the NavBar/index.jsx component and make use of the logout API
that we added before to make the logout logic complete. Replace the whole
content of NavBar/index.jsx with this code:
We are making use of the setToLocalStorage helper method created in the
previous section to clear the localStorage. The user will be then redirected to
the login page if the logout was successfully done in the server too, where we
destroy the @current_user instance variable.
Now let's commit these changes:
Storing information about who created the task
As discussed earlier, let's add a new column task_owner_id to tasks table
using a new migration. We will also add a foreign key constraint on
task_owner_id column which will contain the referenced primary key from a
record in the users table.
Run the following command to generate the migration:
The on_delete: :cascade option makes sure that the referencing rows, which
here are the rows of the task table, also get deleted when deleting the rows of
the referenced table, which is the user table in this case.
In our case, when deleting users, all the tasks created by them will also get
deleted.
Note that rows of users table won't be affected when tasks are deleted. The
foreign key relation is unidirectional.
Again, once we create a new migration, let's persist that into our database:
Adding association for task owner
Every task in the database is associated with the user who created it and a user
can create multiple such tasks. Hence we should declare appropriate associations
in the User and Task models.
Add the following line of code inside app/models/user.rb to add a has_many
association called created_tasks for the User model:
Similarly we should update the Task model and add a belongs_to association
called task_owner like this:
Now we have created associations for both task owner and the assigned user. We
know that all tasks will be deleted when the user who created those tasks is
deleted.
But what happens when the assigned user is deleted? Ideally the task should be
assigned back to the task owner but in our case the assigned user will be set to
nil when that happens.
To fix this, let us add a method inside the User model to assign back the
tasks to the task owner in the event of assigned user getting deleted. Update
app/models/user.rb with the following lines of code:
When assign_tasks_to_task_owners is invoked on a user object, all the
assigned_tasks for that user are fetched and out of those tasks we are
selecting only those tasks which are created by another user and saving the
result in tasks_to_be_reassigned variable.
Tasks owned by the user should be deleted and it doesn't make sense for us to
perform the reassigning operation on a task which is about to be deleted from
the database. Besides if the task_owner and the assigned_user are same then
reassigning isn't required.
By default before_destroy callback method which is
assign_tasks_to_task_owner will be invoked every time, right before a user
record gets deleted.
Creating a task using Task owner
In our application all tasks are associated to users. We cannot create a task
unless there is no user. Each task is created by a user who is related to the
task using the task_owner association and the task is also assigned to a user.
Task assignee is related to a task using the assigned_user association.
So far we had only been saving tasks with an assigned_user. We also need to
add a task owner for tasks. To do so, we can pass the task_owner_id in the
task_params while creating a new task. While this works, we'd have to
explicitly pass the task_owner_id as parameter.
We know that the task_owner will always be the currently logged in user. In our
application, current_user refers to the currently logged in user. We can use
the current_user.created_tasks association to create a task. This way we do
not have to specify the task_owner_id in task_params. When we use
current_user.created_tasks, Rails knows that the new task's task_owner_id is
equal to current user's id.
Rails knows this because we have specified the task_owner_id foreign key for
created_tasks association in the User model. This saves time, and makes code
more fluent as well as adhering to the business logic.
It's generally frowned upon to use Model.new for creating new records.
Whenever possible use the already available association or instance variables
itself.
Update the create method of TasksController like this:
Before moving on, we should delete the existing task records in our database as
they do not have a task_owner and this can lead to errors later on. Use the
following command to delete all the existing task records and their associations
from the database.
Showing task owner
task_owner can be accessed in the show action's corresponding Jbuilder
template file where we can include it as a key in the response JSON along with
other task attributes.
Let's update the show.json.jbuilder view template for tasks and add the
task_owner key to the response JSON. To do so, add the following lines to
/app/views/tasks/show.json.jbuilder:
Now, update the Show component to display the task owner name along with other
task details. Add the following lines of code to the Show component:
Now let's commit these changes: