Progressing my ToDoList app - making it work, making it right
Source: On picking a perfect password by Euroscientist
Since I have a sieve-like memory, I really need to take detailed notes and practice practice in order to remember how to do things. Hence, I am trying to apply what we’ve learned through lectures in my ToDoList ‘practice’ app, especially on areas that I haven’t had a chance to personally code in our current group ‘Hand Raise’ project.
At the end of my previous blog post on my ToDoList app, my next steps were to:
1) Change the navigation links so they are conditional
2) Add authorization for Users to create, edit and delete tasks
But before authorizing users, we first need to authenticate users, and this will be the main body of my post (making it work). The remainder of my post (making it right) is about refactoring, conditionally displaying links and some validation pitfalls that tripped me up.
Making it work - Rails 3 Authentication in 4 parts:
1) Leveraging Rails 3 functionality
Rails 3 includes a has_secure_password class method which simplifies authentication for the beginner and/or the gem-shy.
i) Generate a User model which includes a password_digest string field, which is required to leverage the has_secure_password functionality
ii)Add has_secure_password in the User model
iii) Include (or uncomment) the bcrypt-ruby gem in the Gemfile
2) Creating logins and sessions
The act of logging in is the start of a new user session, which is a virtual model, which means (per my interpretation), that there are specific rules that govern each session, but that there is no need for an actual model, because nothing is persisted into the database.
i) Generate a sessions controller: rails g controller sessions
ii) Create a login form in view. Note that you should use the form_tag helper instead of the form_for helper because there is no sessions model
iii)In the Sessions controller, write a create method, which says:
if a user exists and the password can be authenticated (note that .authenticate
is a method given by has_secure_password
), then set the user’s id as the user_id of the session.
1 2 3 4 5 6 7 8 9 |
|
iv) Logging out would simply entail setting the session’s user_id to nil: session[:user_id] = nil
v) Logging out in views is a bit tricky:
1
|
|
–> even though the session destroy
method does not accept any arguments, the session_path
expects an argument, so just put any argument in
–> need to include method: “delete” (note that method: :destroy
doesn’t work)
3) Allowing the application remember that a user logged in
Create an instance variable to allow the app to remember who the current user is.
i) In Application Controller (so it is set app-wide), create a private method to store the session’s user_id as the current_user, if session user_id exists.
1 2 3 4 |
|
–> add a helper_method
to allow the method to be accessible to all the Views
4) Allowing the application to test if a user is logged in
i) In ApplicationController, create a private method logged_in? or authorize (or anything that is sensibly-named) that tests if a user is logged in.
Either:
1
|
|
Or:
1
|
|
ii) In each relevant controller (in this case, Lists, Tasks, Users), create a before_filter which runs on specific actions with options, e.g.
1
|
|
or:
1
|
|
Making it right - refactoring authentication, conditionally displaying links, validation pitfalls
1) Refactoring authentication
i) Prettifying routes
To change routes from users/new, sessions/new etc. to signup and login, we need to change the routes and then update the views.
Adding the following routes to the routes file…
1 2 3 4 |
|
…enables us to refactor our code in Views from…
1 2 3 |
|
to…
1 2 3 |
|
ii) Further abstracting methods
We can encapsulate the logic of setting session user_ids in private methods in the ApplicationController:
1 2 3 4 5 6 7 |
|
We can then refactor the SessionsController to use these methods.
2) Conditionally displaying links
I wanted to hide the navigation links for a page if a user is already on that page. The Rails UrlHelper from the ActionView::Helpers were very useful in this regard.
Methods such as link_to_if
, link_to_unless
and link_to_unless_current
provide a lot of options for toggling links on and off.
But to completely hide a link, when a user is on a specific page, I used the current_page?
method as follows:
1 2 3 |
|
3) Validation pitfalls
In this set of iterations, I have also incorporated additional front-end and back-end validations that we learned a couple of weeks ago.
One problem I encountered was with password validation, where I initially applied
1
|
|
This broke my ability to assign a user to a task when creating and adding a task to a list. And that took my AGES to debug. Finally, I realized that the user wasn’t saving because the password validation was failing. I got around it by only validating the password on creation:
1
|
|
Another issue I grappled with was the case-sensitivity of emails. I came across :case_sensitive => false
, but some (admittedly dated) blogs have suggested that it slows down applications, so I have downcased the emails when creating a user, and also downcased the email input when creating a session.
1 2 3 4 5 |
|
1 2 3 4 |
|
Resources:
1) ActiveModel at http://api.rubyonrails.org/
2) Authentication from scratch prior to Rails 3: RailsCasts / ASCIIcasts
3) ActionView at http://api.rubyonrails.org/