Adding a field to your sign-up form with Devise

casseylottman

Cassey Lottman

Posted on February 19, 2021

Adding a field to your sign-up form with Devise

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.

  1. Add gem 'devise' to your Gemfile
  2. Run bundle install to install it
  3. 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 in config/environments/development.rb.
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 %>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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' }
Enter fullscreen mode Exit fullscreen mode

Okay, back to Users::RegistrationsController.

Uncomment the following lines:

before_action :configure_sign_up_params, only: [:create]
Enter fullscreen mode Exit fullscreen mode
def create
  super
end
Enter fullscreen mode Exit fullscreen mode
def configure_sign_up_params
  devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
end
Enter fullscreen mode Exit fullscreen mode

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])
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 editing 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>
Enter fullscreen mode Exit fullscreen mode

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!

💖 💪 🙅 🚩
casseylottman
Cassey Lottman

Posted on February 19, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related