Override devise registrations controller
Asked Answered
D

6

244

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?

Degradation answered 23/8, 2010 at 9:32 Comment(1)
I actually wrote a whole blog post on this jacopretorius.net/2014/03/…Unionist
B
363

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"}
Bogtrotter answered 24/8, 2010 at 4:25 Comment(11)
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.Vinegarroon
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:viewsBogtrotter
the above hack doesn't work with devise 1.0.8 which is the version works for rails 2.Squish
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).Blown
Alternatively you can leave your devise views where they are and add paths.app.views << "app/views/devise" in your config/application.rb.Bogtrotter
THis doesn't seem to work in devise 1.4.2. See my question: #6660055Opalescent
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: #16747498Loculus
also note that recent versions of devise allow to use super with a block that yields the resource.Vanburen
so if i don't create an action in the controller subclass, the parent action is automatically used instead? Is that how it works?Orchardman
I had to devise_for :users, controllers: {registrations: 'users/registrations' }Coles
Do you know how to access the submitted "user" resource in create controller?Aribold
O
67

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.

Our answered 18/3, 2011 at 16:12 Comment(3)
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?Loculus
@Our 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 appreciatedOrchardman
MyDevise::RegistrationsController < Devise::RegistrationsController creates a circular dependency error. Am I doing something wrong?Contrabandist
P
34

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" %>
Pass answered 24/12, 2010 at 15:28 Comment(3)
Moving the build logic from the view to the model would be cleaner, see stackoverflow.com/questions/3544265#3764837Obstetrics
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?Tion
How are you able to access the local variable resource in the view instead of a class instance variable @resource?Reduplicate
C
17

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 !

Cnemis answered 22/7, 2016 at 1:48 Comment(0)
A
14

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"}
Ashkhabad answered 6/3, 2016 at 13:36 Comment(0)
T
0

I landed here because I was trying to customize the params that devise permits on signup.

I used this answer to create the custom controller, and then tried permitting the attribute, timezone like so:

def configure_sign_up_params
  devise_parameter_sanitizer.permit(:sign_up, keys: [:timezone])
end

It didn't work until I ALSO uncommented the line at the top of the generated controller:

before_action :configure_sign_up_params, only: [:create]

Truckload answered 26/3, 2022 at 20:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.