jdersen jdersen - 1 year ago 74
Ruby Question

Rails - Devise - Add profile information to separate table

I am using Devise to build a registration/authentication system into my application.

Having looked at quite a few resources for adding information to the devise model (e.g. username, biography, avatar URL, et cetera..) [resources include Jaco Pretorius' website, this (badly formed) SO question, and this SO question.

That's all fine and well -- it works. But my problem is that it's saving to the User model, which, according to database normalizations (also referencing this SO question), it should in fact be saving to a sub-model of User which is connected via


Thus far, I have created a
model via Devise. I have also created a
model via the
rails generate

user.rb (for reference)

class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable, :validatable

has_one :user_profile, dependent: :destroy


class UserProfile < ActiveRecord::Base
belongs_to :user


class CreateUserProfiles < ActiveRecord::Migration
def change
create_table :user_profiles do |t|
t.string :username, null: false
t.string :biography, default: ""

t.references :user, index: true, foreign_key: true

t.timestamps null: false
add_index :user_profiles, [:user_id, :username]

My question, now, is, how does one collect the information for both of these models and ensure, via the devise registration form, that it all ends up in the right places?

I've seen resources about creating state machines (AASM, and the answer to this SO question. I've also seen information about creating a wizard with WICKED, and an article on the same topic.

These all seem too complicated for my use-case. Is there some way to simply separate the inputs with devise and make sure the end up in the right place?

Answer Source

I think, instead of simply commenting on an answer that led me to the final answer, I'll archive the answer here in case someone in the future is trying to also find this answer:

I will be assuming that you have some sort of setup as I do above.

First step is you need to modify your User controller to accept_nested_attributes_for the profile reference as well as add a utility method to the model so when requested in code, the application can either retrieve the built profile model or build one.

The user model ends up looking like so:

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable, :validatable

  has_one :user_profile, dependent: :destroy
  accepts_nested_attributes_for :user_profile

  def user_profile
    super || build_user_profile

Secondly, you will need to modify your sign up/account_update form to be able to pass the attributes for this secondary model into the controller and eventually to be able to build the profile for the parent model.

You can do this by using f.fields_for.

Add something like this to your form:

<%= f.fields_for :user_profile do |user_profile_form| %>
 <%= user_profile_form.text_field :attribute %>
<% end %>

An example of this in my specific case is:

<%= f.fields_for :user_profile do |user_profile_form| %>
  <div class="form-group">
    <%= user_profile_form.text_field :username, class: "form-control", placeholder: "Username" %>
<% end %>

Finally, you will need to tell Devise that it should accept this new hash of arguments and pass it to the model.

If you have created your own RegistrationsController and extended Devise's, it should look similar to this:

class RegistrationsController < Devise::RegistrationsController
    def sign_up_params
      params.require(:user).permit(:email, :password, user_profile_attributes: :username)

(Of course, make the proper changes for your specific use-case.)

If you have simply added the Devise sanitization methods to your application controller, it should look similar to this:

class ApplicationController < ActionController::Base
  before_filter :configure_permitted_parameters, if: :devise_controller?

    def configure_permitted_parameters
      devise_parameter_sanitizer.for(:sign_up) {|u|
        u.permit(:email, :password, user_profile_attributes: :username)}

(Again, make the proper changes for your specific use-case.)

A small note on user_profile_attributes: :username: Note this is a hash, of course. If you have more than one attribute you are passing in, say, as an account_update (hint hint), you will need to pass them like so user_profile_attributes: [:attribute_1, :attribute_2, :attribute_3].

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download