Adding a field to your sign-up form with Devise
Tags: ruby, rails, devise
Published: 2021-02-19 22:39:48

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](https://github.com/heartcombo/devise#getting-started) 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`.  
```ruby
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: 
```ruby
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. 

```html
<% 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: 
```ruby  
devise_for :users, :controllers => { registrations: 'users/registrations' }
```

Okay, back to `Users::RegistrationsController`. 

Uncomment the following lines: 
```ruby
before_action :configure_sign_up_params, only: [:create]
```

```ruby
def create
  super
end
```

```ruby
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.  

```diff
-   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!