Topic: Writing to a second model when using Devise

Hi everyone,

I'm trying to make a simple app where users can sign up and will have a basic profile page that is unique to their account.

I'm using Devise for the authentication, and I have created a Profile model where individual user information will be stored.

I want Devise to create a new record in the Profiles table when a new user is created. I believe I need to edit the create action in the Devise controller but don't know how to access that.

I read that I should create a controller for my model (Which in this case is ProfilesController), which I did. I've got the following controller code:

class ProfilesController < Devise::RegistrationsController
  
  def new
    @user.profile = Profile.new
  
  def create
    @profile = @user.profiles.build(params[:profile])
      if @project.save
        redirect_to :action => 'index'
      else
        render :action => 'new'
      end
  end

end

The code in my profile.rb and user.rb is:

#profile.rb
class Profile < ActiveRecord::Base
  attr_accessible :name
  
  belongs_to :user
end

#user.rb
class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable, :lockable and :timeoutable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  
  # Setup accessible (or protected) attributes for your model
  attr_accessible :name, :email, :password, :password_confirmation, :remember_me
  
  has_one :profile
end

And my signup page for Devise is currently:

<h2>Sign up</h2>

<% resource.build_profile %>
<%= form_for(resource, :as => resource_name, 
             :url => registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>


  <p><%= f.label :name %><br />
  <%= f.text_field :name %></p>
  
  <p><%= f.label :email %><br />
  <%= f.text_field :email %></p>

  <p><%= f.label :password %><br />
  <%= f.password_field :password %></p>

  <p><%= f.label :password_confirmation %><br />
  <%= f.password_field :password_confirmation %></p>
  
    <%= f.fields_for :profile do |profile_form| %>
      <p><%= profile_form.label :name %><br />
      <%= profile_form.text_field :name %></p>

    <% end %>

  <p><%= f.submit "Sign up" %></p>
<% end %>

<%= render :partial => "devise/shared/links" %>

(I have two name fields just so I can see that it writes it to Profile, but I would like to combine it into one field but written into two models, User and Profile).

So right now it's not working, and I think I need to get access into the Devise controller but I don't know how to do that. I want the user signup process to be linked to a new profile creation, and some of the information collected in the signup process to be written to that new profile record which is linked directly (so I can handle permissions etc later) to that particular user.

Any tips or guidance would be much appreciated.

Thanks for the help.

- Raoul

Re: Writing to a second model when using Devise

Hi Raoul,

I've just been through this myself, I think.  You will need to have had a good read of controller overriding in the Devise page in GitHub for this to make any sense though.

First off in your controller you need to override the method, critically though you need to use the statement 'super' to ensure that the underlying code is run, for example ...

class RegistrationsController < Devise::RegistrationsController
 
  def create
    super
    @profile = Profile.new
    Now put the data into your profile record
    @profile.save   
  end

end

Alternatively though, if you want to make things nice and simple just store the extra profile data in the existing Devise model, just make sure that in your model you declare the additional fields in your attr_accessible, for example ...

attr_accessible :email, :password, :password_confirmation, :remember_me, :name, :company

Let me know how you get on.

Richard.
www.creativetechnologist.co.uk

Re: Writing to a second model when using Devise

Hi Richard,

Thanks for the response. I tried playing with it to no avail, so started over using the create_after callback.

# user.rb
class User < ActiveRecord::Base
.
.
  
  has_one :profile
  
  after_create :add_profile
  
  accepts_nested_attributes_for :profile
  
    private

  def add_profile
    Profile.create(:user => self)
  end
end

And in the edited registration page for Devise:

.
.  <%=f.fields_for :profile do |profile_form| %>
      <p><%= profile_form.label :name %><br />
      <%= profile_form.text_field :name %></p>

  <% end %>

  <p><%= f.submit "Sign up" %></p>
<% end %>

Just by using the after_create callback, a record is created. Which is a good step forward. But nothing gets written to the Profile table (all values are nil). I've set the attr_accessible for all parameters (including the :name as per the form above.)

So a few new questions:

1. How do I check that the new user created is linked to the profile record (is there a foreign key, etc. Is there a command I can use in the console to see this?)

2. Why is it not writing the record to the Profile table? I assume it has something to do with my Profiles controller (code below).

class ProfilesController < UsersController
  def new
    super
    @user.profile = Profile.new
  end

  def create
    super
    @profile = @user.profiles.build(params[:profile])
      if @profile.save
      flash[:notice] = "Profile written"
      redirect_to :action => 'index'
    else
      render :action => 'new'
    end
  end

end

Thanks again for any help/guidance!

- Raoul

Re: Writing to a second model when using Devise

FIGURED IT OUT!! (Solution below for others who might have similar situations)

The after_create callback was unnecessary. The Profiles Controller was also irrelevant for the CREATION of the record (If you want to have a "Profile Page", you'll need to define a Show action in a Profiles Controller, but I just am using the Users show page).

# user.rb
class User < ActiveRecord::Base
.
.
    attr_accessible :email, :password, :password_confirmation, :remember_me, 
            :location, :profile_attributes

  has_one :profile
  
  accepts_nested_attributes_for :profile
  
end

#profile.rb
.
.
belongs_to :user

The only two things I needed to do was to specify :profile_attributes as attr_accessible in my User.rb, AND add a column called user_id to my profile table (note, userid didn't work).

Everything worked fine after that. What a wonderful 2 days!

Re: Writing to a second model when using Devise

@raoulalwani I am trying to implement the code u have laid out in this post, and am not having any luck. Would you ming double checking? I have...

#profile.rb
  belongs_to :user

#user.rb
class User < ActiveRecord::Base
  has_one :profile
  accepts_nested_attributes_for :profile

  # Include default devise modules. Others available are:
  # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :password, :password_confirmation, :remember_me, :profile_attributes
end

View is here: http://pastie.org/2740686

Here is what I am seeing in the console: http://pastie.org/2740688

The params for the profile are there, but nothing is getting saved. What am I missing? Thanx!

Last edited by lumbee (2011-10-22 11:01:33)

Re: Writing to a second model when using Devise

Hmm, interesting -- try adding this:

<% resource.build_profile %>

above <%= form_for(resource, :as etc etc etc

in your view?