- Read Tutorial
Why Start with Authentication?
In picking out what features to start building first I try to prioritize features based on dependencies. By dependencies I mean what feature has the most other features depend on it?. Thankfully we did a good job in creating our requirements document, which means that we can look at each of the functional requirements to see what feature the other modules in the application will rely on.
Do you notice a trend? Over half of the entire application requirements seem to be based on User
requirements, so that is where we'll start. If we waited to integrate other features, such as the Post
functionality we'd have to go back later on and refactor the posting feature to reference the User
model.
In addition to analyzing the functional requirements, I also look at the proposed database schema. Do you notice how many times the other tables will have a user_id
foreign key? That's also a good sign that we should build our User
model should be the first feature to build.
Devise or Auth from Scratch?
I've been building applications for quite a while and I still have only had two applications that had to have authentication created from scratch as opposed to using a gem like Devise
and both of those times were due to a requirement to integrate Microsoft's ActiveDirectory auth engine.
My personal opinion on this is that I prefer to use well tested and constantly maintained gems like Devise
over building a feature from scratch, assuming that the gem is flexible and doesn't inhibit any future features for the application. I'm going to go through each of the steps needed for integrating Devise
, but I'm not going to go into detail on the gem itself, I already wrote a Devise gem review that you want some more detailed information about the gem itself.
Implementing Authentication
We could start by creating some model specs, however I don't like writing specs to test functionality that I know is going to pass, Devise
has a comprehensive test suite and it would be a waste of our time to duplicate the tests that they already have in place. You can see the gem test suite here.
Before we start building the feature, it's important that we're not working on the master branch of our repo, so first create a new branch by running the terminal command:
git checkout -b add-auth
This is important because we need to have the ability to isolate the work we're doing so it doesn't touch the master branch of our repo, even locally. I can't tell you how many times I've been working on a feature and right when I'm in the middle of it I'm asked to perform an urgent task, such as fixing a bug on the live application. If I didn't check my new work into a separate branch I'd have to:
Reset to a different commit (and potentially cause conflicts)
Comment out my changes (this is a horrible idea, don't do it, you'll inevitably forget to comment one line out that breaks the entire application)
Manually remove all of the changes (also very error prone and I'd lose all my hard work!)
Now that we're safely working on an isolated branch, let's install the gem by adding it to the gemfile:
# Gemfile gem 'devise', '~> 3.5', '>= 3.5.5'
After running bundle
we can run the installer:
rails generate devise:install
First let's update the default mailer_sender
configuration setting in config/initializers/devise.rb
to ensure that auth emails sent from the application reference our app name and not the default address:
# config/initializers/devise.rb config.mailer_sender = 'team@dailysmarty.com'
Let's go through the standard configuration steps:
Update the local mail server
# config/environments/development.rb config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
Update the local mail server
# config/environments/development.rb config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
We can skip the instructions for the homepage since we did that in the Rails App Configuration lesson. Now let's add in the alerts. I don't add the alerts to the master layout file since there could be pages where we don't want the alerts showing up, or we may want them rendered on a special spot on the page. So let's create a view directory called shared/
and add in a partial called _alerts.html.erb
:
<%#= app/views/shared/_alerts.html.erb %> <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p>
Now we can simply add a render call in the application layout file, and we'll remove it when we start to implement the design:
<%#= app/views/layouts/application.html.erb %> <%= render 'shared/alerts' %>
Notice how much more cleaner this is? As we build this application you'll see that we'll be using partials quite a bit to ensure our views stay as clean and DRY as possible. Now let's create our views by running the following terminal command:
rails g devise:views
This will create all of the view files that we'll need to manage:
Registrations
Sign in
Editing accounts
Requesting a password reset
Mailer templates
I like the Devise
generator, however it does create a few files that we don't need, let's remove those so our app doesn't start getting cluttered with code that we're not going to use:
rm -rf app/views/devise/confirmations rm -rf app/views/devise/mailer/confirmation_instructions.html.erb
We're not going to be using the confirmation email workflow provided by Devise
so there's no need to keep these files.
Now let's create our User model:
rails g devise User first_name:string last_name:string avatar:text username:string
This will create the default User
model provided by the gem and I also appended the first_name
, last_name
, avatar
, and username
attributes so they will get added to the users
table. Now we can run rake db:migrate
to create the table and update the schema. Opening the schema file up we can see our table and attributes are all listed:
# db/schema.rb create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.inet "current_sign_in_ip" t.inet "last_sign_in_ip" t.string "first_name" t.string "last_name" t.text "avatar" t.string "username" t.datetime "created_at", null: false t.datetime "updated_at", null: false end add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
This will get us everything we need for users to start registering, signing in, and other standard authentication feature tools. In the next lesson we're going to walk through building custom functionality into our auth system.