Search code examples
ruby-on-railsruby-on-rails-3devisesingle-table-inheritancesti

Sign in inherited user after registration with Devise


I am using rails 3.2.14 and devise 3.2.1, and I have an "Admin" with an STI relationship with my devise "User" model.

When I register a new Admin with the form below, I get the following error:

undefined method `new_admin_session_path' for #<ActionDispatch::Routing::RoutesProxy:0x007fa6fe068f18>

because I have:

devise_for :users, :skip => :registrations do

in my routes.

How can I register a new "Admin" and then sign them in as a "User"?

Please note that if I exchange "Admin" for "User" in my form_for, and remove the skip registrations from "User" in my routes, then I get the behaviour I want apart from it is a "User" not an "Admin".

I have created a stand alone application to demonstrate my problem. https://github.com/deathwishdave/devise_test

user.rb

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

  attr_accessible :email, :password, :password_confirmation, :remember_me
end

admin.rb

class Admin < User
end

index.html.erb

<h2>Sign up</h2>

<%= form_for(Admin.new, :as => :admin, :url => registration_path(:admin)) do |f| %>
  <%= devise_error_messages! %>

  <div><%= f.label :email %><br />
  <%= f.email_field :email, :autofocus => true %></div>

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

  <div><%= f.label :password_confirmation %><br />
  <%= f.password_field :password_confirmation %></div>

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

routes.rb

  devise_for :users, :skip => :registrations do
      delete '/logout', :to => 'sessions#destroy', :as => :destroy_user_session
      get '/login', :to => 'sessions#new', :as => :new_user_session
      post '/login', :to => 'sessions#create', :as => :user_session
  end

  devise_for :admins, :skip => :sessions

  authenticated :user do
    root :to => "dashboard#index"
  end

  authenticated :admin do
    root :to => "dashboard#index"
  end

  root :to => 'welcome#index'

Update

Requirements…

  1. Four different user roles.
  2. Ability to easily add additional roles in the future.
  3. Single log-in/sign-in form for all roles.

Additional Information.

  1. All current and future roles share the same state (have the same DB fields)
  2. Every role has different relationships with other model classes.

To satisfy these requirements, I considered multiple options including STI, polymorphic associations, authorisation gems and just separate models and tables.

polymorphic associations are a poor choice because the fields between roles are the same, and it may cause difficulties with user creation. Authorisation gems such as cancan are not an option, as the roles have different relationships with other model classes. If I were to use cancan, the User model would be polluted by relationships that don’t apply to all users. Having separate models, and tables would not allow for a single point of entry for all types of user.

STI seems like a good fit because all role fields are the same, so the database will be normalised. Additional users can be added with ease.

And from a purist perspective, it makes sense. My different roles are all users, with a user being an interface or contract for all other specialties.


Solution

  • STI vs polymorphic

    Based on the requirements and information you give about the user does STI seems like fine solution, especially if there is different logic associated to each user. However polymorphic associations would work fine as well, even if each roles doesn't have any other database informations (see http://railscasts.com/episodes/394-sti-and-polymorphic-associations).

    Note on CanCan

    Correct me if im wrong but CanCan seems like a good solution still, also with STI. CanCan is roled based so you can check what type of user you are authorizing and then create the rules (with relations) based on that user type/role.

    Login admin as user

    To answer you actual question on how to login a admin as a user should you be able to simply override the method sign_up in registration controller, like this:

    class Admin::RegistrationsController < Devise::RegistrationsController
      def sign_up(resource_name, resource)
        sign_in(:user, resource)
      end
    end
    

    and add the following to your routes:

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

    Also i find this link about STI and devise, it's a little different scenario than yours, but properly still some useful hints for you: http://adamrobbie.me/blog/2013-3-29-sti-with-rails-40-beta-and-devise