Adding custom fields to your Devise User model in Rails 4

So, you’ve set up a new Rails 4.0.0rc2 project with Devise 3.0.0.rc. Look at you, cutting-edge-developer-person! You’ve set up a user model, you can register, you can log in, you can log out. Awesome.

But now you want to add some additional columns to your user model. A first name, a last name–nothing too crazy. You create your migration:

<code>class AddNameToUsers &lt; ActiveRecord::Migration
  def change
    add_column :users, :first_name, :string
    add_column :users, :last_name, :string
  end
end
</code>

You migrate your database, and you add the appropriate fields to your edit form:

<code>h2
  | Edit Your Profile
= form_for(resource, :as =&gt; resource_name, :url =&gt; registration_path(resource_name), :html =&gt; { :method =&gt; :put }) do |f|
  = devise_error_messages!
  div
    = f.label :first_name
    = f.email_field :first_name
  div
    = f.label :last_name
    = f.email_field :last_name
  div
    = f.label :email
    = f.email_field :email
  div
    = f.label :password
    = f.password_field :password
  div
    = f.label :password_confirmation
    = f.password_field :password_confirmation
  div
    = f.label :current_password
    = f.password_field :current_password
  div= f.submit "Save", class: %w( btn btn-large btn-block btn-success )
 
p
  | #{link_to "Delete my account", registration_path(resource_name), :confirm =&gt; "Are you sure?", :method =&gt; :delete, class: "warning"}
</code>

But when you actually try to add your name to your account, the values in the first name and last name fields aren’t persisted. Even more annoying, everything happened without throwing an error.

If this article were about Rails 3, the next thing I’d mention is attr_accessor. But in Rails 4, attr_accessor has been deprecated in favor of strong parameters.

Strong Parameters

Strong parameters are the new approach to avoiding mass assignment in Rails 4. Instead of defining which fields are modifiable using attr_accessor in your model, you provide a whitelisted set of params in your controller. It looks something like this (which is an example from the strong parameters gem documentation):

<code>class PeopleController &lt; ActionController::Base
  def create
    Person.create(params[:person])
  end
 
  def update
    person = current_account.people.find(params[:id])
    person.update_attributes!(person_params)
    redirect_to person
  end
 
  private
 
    def person_params
      params.require(:person).permit(:name, :age)
    end
end
</code>

The person_params method is the one we’re interested in. Note how it calls .permit and passes in a set of accessible fields.

Using this as an example, you might think that the way to enable the first_name and last_name fields on your user model would look something like this:

<code>def user_params
  params.require(:user).permit(:first_name, :last_name)
end
</code>

It’s a good assumption, but since we’re using Devise, it’s a bit trickier. To understand why, we need to look at how Devise implements parameter sanitization when strong parameters are in use.

How Devise Handles Params

Standard CRUD functionality for user accounts is provided by Devise’s RegistrationsController. If you take a peek at the code, you’ll see create and update methods. We’re interested in the update method, specifically this line:

<code>if resource.update_with_password(account_update_params)
</code>

The account_update_params method is declared in the protected section at the bottom of the RegistrationsController. It hands off the parameters (which are generated by a call to account_update) to a sanitization object:

def account_update_params
  devise_parameter_sanitizer.for(:account_update)
end

If we follow the trail to the devise_parameter_sanitizer method, we find this:

<code>def devise_parameter_sanitizer
  @devise_parameter_sanitizer ||= if defined?(ActionController::StrongParameters)
    Devise::ParameterSanitizer.new(resource_class, resource_name, params)
  else
    Devise::BaseSanitizer.new(resource_class, resource_name, params)
  end
end
</code>

Finally! A reference to strong parameters.

If running in a Rails 4 app, Devise uses a subclass of its BaseSanitizer object called ParameterSanitizer. And if you take a look at ParameterSanitizer, you should see something familiar:

<code>def account_update
  default_params.permit(auth_keys + [:password, :password_confirmation, :current_password])
end
</code>

Here we see .permit being called on a set of parameters. This is the method that we need to override in order to be able to modify our user model’s custom fields.

How-To

We need a subclass of ParameterSanitizer to hold our account_update method, and we need a way to tell our app to use it when a person tries to update his or her account.

We’ll start by creating the ParameterSanitizer subclass. Create a file named user_sanitizer.rb in the lib directory. Edit it, and drop in your account_update method that includes your custom fields:

<code>class User::ParameterSanitizer &lt; Devise::ParameterSanitizer
    private
    def account_update
        default_params.permit(:first_name, :last_name, :email, :password, :password_confirmation, :current_password)
    end
end
</code>

Next, to make our app rely on our custom sanitizer, we need to override the devise_parameter_sanitizer method. The Devise docs show an example of how to do this by editing your ApplicationController:

<code>class ApplicationController &lt; ActionController::Base
  protected
 
  def devise_parameter_sanitizer
    if resource_class == User
      User::ParameterSanitizer.new(User, :user, params)
    else
      super
    end
  end
end
</code>

And finally, we need to load our sanitizer so that our ParameterSanitizer subclass is available in our app. Previously, you probably would have configured your Rails app to autoload everything in the lib directory. Rails 4 prefers that you use an initializer, so we’ll do that.

Create a file in config/initializers called sanitizers.rb. Edit it, and add this line:

<code>require "#{Rails.application.root}/lib/user_sanitizer.rb"
</code>

If, in the future, you decide to customize Devise further by adding additional roles, just add another sanitizer to the lib directory–admin_sanitizer.rb, maybe–and include it in your initializer.

Save the initializer, restart your server, and try to edit your account again. You’ll see that your custom fields are persisted. Hooray!

What’s Next

That’s a lot of steps. Luckily, once it’s configured, you shouldn’t have to bother with it again (except to add additional fields if you further customize your user model). And if you decide you want to do something like add the first and last name to the signup form, you can easily use the same technique; just override the sign_up method in user_sanitizer.rb.

If you want to know more about strong parameters and how they’re used outside the scope of Devise, I highly recommend Episode 371 of Railscasts.