true

I have added a field to the sign-up form that is based on a different model, see How do I use nested attributes with the devise model for the gory details. This part is working fine.

The problem now is when I save, it is failing in the create action of the registrations controller that is supplied by devise with an Activerecord::UnknownAttributeError on this field (company).

I am assuming I need to override the registrations controller, or is there a better/easier way I should be approaching this?

7 Answers 11

up vote 326 down vote accepted

In your form are you passing in any other attributes, via mass assignment that don't belong to your user model, or any of the nested models?

If so, I believe the ActiveRecord::UnknownAttributeError is triggered in this instance.

Otherwise, I think you can just create your own controller, by generating something like this:

# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
  def new
    super
  end

  def create
    # add custom create logic here
  end

  def update
    super
  end
end 

And then tell devise to use that controller instead of the default with:

# app/config/routes.rb
devise_for :users, :controllers => {:registrations => "registrations"}
3 upvote
  flag
But how do you make sure devise looks in the devise dir for the views? I'm trying this but devise methods like "sign_in_and_redirect(resource_name, resource)" are looking in views for the template. – AnApprentice
7 upvote
  flag
If you want to customise your devise views, you just need to generate them first and devise will check your views folder before loading the views from the gem. In Rails 3 it's: rails generate devise:views and in Rails 2 (i think) it's: script/generate devise:views – theTRON
2 upvote
  flag
the above hack doesn't work with devise 1.0.8 which is the version works for rails 2. – William Yeung
17 upvote
  flag
If you override a Devise controller like this, make sure you copy all views from app/views/devise/registrations to app/views/registrations/ (change for whichever controller you're overriding). – Jamie Cobbett
30 upvote
  flag
Alternatively you can leave your devise views where they are and add paths.app.views << "app/views/devise" in your config/application.rb. – theTRON
upvote
  flag
THis doesn't seem to work in devise 1.4.2. See my question: //allinonescript.com/questions/6659555/… – David Tuite
upvote
  flag
That's the approach I'm taking, but I don't have a clue what to put in there since Devise uses this resource stuff. Doesn't seem to be congruent with my already-working create method in my own controller. My post is here: //allinonescript.com/questions/16746998/… – Kyle Carlson
2 upvote
  flag
also note that recent versions of devise allow to use super with a block that yields the resource. – m_x
upvote
  flag
so if i don't create an action in the controller subclass, the parent action is automatically used instead? Is that how it works? – BKSpurgeon

I believe there is a better solution than rewrite the RegistrationsController. I did exactly the same thing (I just have Organization instead of Company).

If you set properly your nested form, at model and view level, everything works like a charm.

My User model:

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

  has_many :owned_organizations, :class_name => 'Organization', :foreign_key => :owner_id

  has_many :organization_memberships
  has_many :organizations, :through => :organization_memberships

  # Setup accessible (or protected) attributes for your model
  attr_accessible :email, :password, :password_confirmation, :remember_me, :name, :username, :owned_organizations_attributes

  accepts_nested_attributes_for :owned_organizations
  ...
end

My Organization Model:

class Organization < ActiveRecord::Base
  belongs_to :owner, :class_name => 'User'
  has_many :organization_memberships
  has_many :users, :through => :organization_memberships
  has_many :contracts

  attr_accessor :plan_name

  after_create :set_owner_membership, :set_contract
  ...
end

My view : 'devise/registrations/new.html.erb'

<h2>Sign up</h2>

<% resource.owned_organizations.build if resource.owned_organizations.empty? %>
<%= 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 :username %><br />
    <%= f.text_field :username %></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 :owned_organizations do |organization_form| %>

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

    <p><%= organization_form.label :subdomain %><br />
      <%= organization_form.text_field :subdomain %></p>

    <%= organization_form.hidden_field :plan_name, :value => params[:plan] %>

  <% end %>

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

<%= render :partial => "devise/shared/links" %>
2 upvote
  flag
Moving the build logic from the view to the model would be cleaner, see //allinonescript.com/questions/3544265#3764837 – meleyal
upvote
  flag
I generated the devise controllers and now have controller action create triggered when user clicks Sign up. Is there a way(like overriding / some example code) I can use Devise to encrypt the password and do backend checks of the password and other fields? and saving it to the model database? – H P
upvote
  flag
How are you able to access the local variable resource in the view instead of a class instance variable @resource? – Chloe

A better and more organized way of overriding Devise controllers and views using namespaces:

Create the following folders:

app/controllers/my_devise
app/views/my_devise

Put all controllers that you want to override into app/controllers/my_devise and add MyDevise namespace to controller class names. Registrations example:

# app/controllers/my_devise/registrations_controller.rb
class MyDevise::RegistrationsController < Devise::RegistrationsController

  ...

  def create
    # add custom create logic here
  end

  ...    

end 

Change your routes accordingly:

devise_for :users,
           :controllers  => {
             :registrations => 'my_devise/registrations',
             # ...
           }

Copy all required views into app/views/my_devise from Devise gem folder or use rails generate devise:views, delete the views you are not overriding and rename devise folder to my_devise.

This way you will have everything neatly organized in two folders.

1 upvote
  flag
This is similar to the approach I'm taking, but I don't know what custom logic to put in the create method of Devise's I overwrote. My scaffold-created controller that I modified works great, but how do you make it work with Devise's resource business? – Kyle Carlson
upvote
  flag
@Vincent thank you - if i want to override just one method, do i write just the method i want to override - and will everything else just work as normal? Your assistance much appreciated – BKSpurgeon

You can also make the folder devise in your controllers path and copy/paste the entire devise controller into it. This requires no additional configuration.

So for just the registrations controller it would be app/controllers/devise/registrations_controller.rb and then copy the devise registrations_controller.rb source into it. You can get it from github https://github.com/plataformatec/devise/blob/master/app/controllers/devise/registrations_controller.rb, or you can track it down in your gem source, or if you use RubyMine when you type in the class line class Devise::RegistrationsController a globe like symbol appears to the left... click on it and it opens the Devise Registration Controller file for you. Then copy/paste.

9 upvote
  flag
-1 for promoting copy/paste practices, especially in such a security sensitive context. If a patch is applied to Devise for security reasons, your code would not benefit from it even if you upgrade the gem version. Better use @Vincent's solution and inherit from Devise::RegistrationsController – m_x
upvote
  flag
Plus one because you're right and my answer shows how people can find the source code. – 6ft Dan

create the controller registrations and override its inherited class by predefined Devise::RegistrationsController class

# app/controllers/registrations_controller.rb
class RegistrationsController < Devise::RegistrationsController
  def new
    super
  end

  def create
    # add custom create logic here
  end

  def update
    super
  end
end 

after this set routes to :

# app/config/routes.rb
devise_for :users, :controllers => {:registrations => "registrations"}
2 upvote
  flag
Its okay ! Done. – Jai Kumar Rajput

You can generate views and controllers for devise customization.

Use

rails g devise:controllers users -c=registrations

and

rails g devise:views 

It will copy particular controllers and views from gem to your application.

Next, tell the router to use this controller:

devise_for :users, :controllers => {:registrations => "users/registrations"}

Very simple methods Just go to the terminal and the type following

rails g devise:controllers users //This will create devise controllers in controllers/users folder

Next to use custom views

rails g devise:views users //This will create devise views in views/users folder

now in your route.rb file

devise_for :users, controllers: {
           :sessions => "users/sessions",
           :registrations => "users/registrations" }

You can add other controllers too. This will make devise to use controllers in users folder and views in users folder.

Now you can customize your views as your desire and add your logic to controllers in controllers/users folder. Enjoy !

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