2

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 has_one and belongs_to.

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

user.rb (for reference)

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

  has_one :user_profile, dependent: :destroy
end

user_profile.rb

class UserProfile < ActiveRecord::Base
  belongs_to :user
end

timestamp_create_user_profiles.rb

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
    end
    add_index :user_profiles, [:user_id, :username]
  end
end

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?

0

3 Answers 3

5

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
  end
end

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" %>
  </div>
<% 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
  private
    def sign_up_params
      params.require(:user).permit(:email, :password, user_profile_attributes: :username)
    end
end

(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?

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

(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].

1

Please check out the RailsCasts.com web-site.

There are a couple of interesting railscasts about nested model forms:

http://railscasts.com/episodes/196-nested-model-form-part-1

http://railscasts.com/episodes/197-nested-model-form-part-2

http://railscasts.com/episodes/196-nested-model-form-revised

Also check out accepts_nested_attributes_for

Or check out this question: Profile model for Devise users?

1
  • Your references to the documentation and the other SO question finally led me to the answer. Commented Mar 17, 2016 at 6:56
1

Also note that for Devise 4.2 the '.for' method for the devise_parameter_sanitizer is deprecated in favor of '.permit'

From the documentation:

def configure_permitted_parameters
  devise_parameter_sanitizer.permit(:sign_in) do |user_params|
    user_params.permit(:username, :email)
  end
end

Not the answer you're looking for? Browse other questions tagged or ask your own question.