Cassey Lottman
Posted on February 19, 2021
Devise is the go-to solution for user authentication in Ruby on Rails, and it's extremely customizable. There is documentation galore on everything you can do with it, but when I first started using it, I found it all a bit overwhelming.
I wanted to have a basic sign-up flow and add one custom field that users would be required to enter in order to create an account. Here are the steps I took, starting with installing Devise.
Install Devise
Here we're following the Devise Getting Started guide directly.
- Add
gem 'devise'
to your Gemfile - Run
bundle install
to install it - Run
rails generate devise:install
Read the instructions here and make any changes you need to make - probably setting including setting the default Action Mailer URL inconfig/environments/development.rb
.
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
Step 4 of the instructions, at least at time of writing, tells you to optionally run rails g devise:views
. Don't do this yet; we just want to bring in the views that we intend to customize little by little.
Run the model generator for Devise
If you want to use the User model, you'll run:
rails generate devise User
As mentioned in the getting started guide, this will create a model if one doesn't already exist, and configure it with the default Devise modules.
Check your User model to make sure you like the included modules, and add more if you want to. If you do, you'll need to uncomment the corresponding sections in migration file.
Add the additional column you want to save on sign up
I knew when I was getting set up with Devise that I'd want to save a the user's name (as in Cassey) along with their email and password on sign-up. So, I went ahead and added the column I wanted to the generated devise_create_users
migration. A new user must have a name, so I made it non-nullable.
t.string :name, null: false
You can also create a separate migration to add the extra column if you want, to make it clear what what you added to the base Devise configuration.
When you're ready, run rails db:migrate
.
Run the app.
Test your routes
Run your Rails app, and make sure that you can navigate to the new user registration path - by default /users/sign_up
. When you're there, you should see a simple form that asks the user for their email, their password, and a password confirmation.
You might want something like this in your main layout, so that users can sign in if they're not signed in already, or sign out. The current_user
variable is available automagically thanks to Devise.
<% if current_user %>
<li><%= link_to "Sign out", destroy_user_session_path, method: :delete %></li>
<% else %>
<li><%= link_to "Sign up", new_user_registration_path %></li>
<% end %>
Watch out for the method
on the sign out link - it needs to be DELETE
, and won't work if you try to make the request with POST
.
Bring in the controllers you need to customize
If we try to submit the form at this point, it won't work! We'll see SQLite3::ConstraintException: NOT NULL constraint failed: users.name
.
We need to modify the controller that accepts sign ups, in order to actually save the name
field to the database.
At first, when you install Devise, the views and even the controllers that will be used are all located in the engine itself - not in your app where you can edit them directly. You can use a generator to bring copies into your app that you can edit.
rails generate devise:controllers users -c registrations
This will create a file at controllers/users/registrations_controller.rb
.
In registrations_controller.rb
, we have a class Users::RegistrationsController
that inherits from Devise::RegistrationsController
. All the methods in it are commented out - and as long as you leave them commented out, the behavior of the class will fall back to what's in the parent class, aka Devise's default behaviors.
To make sure this class is actually used for Devise routes, we need to change the settings for devise_for
in config/routes.rb
, like so:
devise_for :users, :controllers => { registrations: 'users/registrations' }
Okay, back to Users::RegistrationsController
.
Uncomment the following lines:
before_action :configure_sign_up_params, only: [:create]
def create
super
end
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
end
We need to make a change here, to the configure_sign_up_params
method, to get rid of the placeholder :attribute
that's helping us know what to put where, and add in the form field that we want to allow through the controller.
- devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
+ devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
Bring the views into your app to customize them
Our sign up form is pretty basic but mostly functional (you'll probably want to customize the design a bit soon), but it's still not asking the user for their name. So, let's copy the view over from the Devise engine using the generator.
rails generate devise:views users -v registrations
After running this command, you should see a new folder under views
: views/users
, which contains ./registrations
(and ./shared
, if this is the first time you've generated views with Devise). The views/users/registrations
folder will have two files, one for edit
ing an account registration, and one for a new
sign up.
In views/users/registrations/new.html.erb
, we see the code that was generating the form we saw earlier. Let's add a field for the user's name
, matching the structure of the generated parts of the form:
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
When you reload the /users/sign_up
in the browser, you should see your new name field! You should be able to submit it at this point, too, since we've already made changes in the controller to allow through the name
parameter.
Happy customization!
Posted on February 19, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.