Single table inheritance and Devise single Login

2k views Asked by At

I am working on a Rails project using single table inheritance and devise.

I have User, Client and Guide models. Client and Guide inherit from the User model. They have different paths for registration but I want them both to be able to log in on the same page.

Currently, neither can log in because I believe the resource is being set to User.

Does anyone knew how I can test a User login to see what type it is (either Client or Guide) and then set it to be that resource and direct it to the correct page?

This is my route:

Rails.application.routes.draw do

  get 'pages/index'

  devise_for :clients, controllers: {registrations: "clients/registrations", confirmations: "clients/confirmations", omniauth: "clients/omniauth", unlocks: "clients/unlocks"}, :skip => [:sessions, :passwords]

  devise_for :guides, controllers: {registrations: "guides/registrations", confirmations: "guides/confirmations", omniauth: "guides/omniauth", unlocks: "guides/unlocks"}, skip: [:sessions, :passwords]
  # devise_for :users, controllers: {passwords: "devise/passwords", sessions: "devise/sessions"}, skip: [:registrations, :confirmations, :omniauth, :unlocks]
  devise_for :users, controllers: {passwords: "devise/passwords"}, skip: [:sessions, :registrations]

  devise_for :admin_users, ActiveAdmin::Devise.config
  ActiveAdmin.routes(self)
  # The priority is based upon order of creation: first created -> highest priority.
  # See how all your routes lay out with "rake routes".

  # You can have the root of your site routed with "root"
  root 'pages#index'

Here is my User model:

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

  validates :type, inclusion: { in: %w(Client Guide)} 

  ####################################################################################

  # make sure that guide information is verfied as only being for a guide
  validate :only_a_guide_has_bio

  validate :only_a_guide_has_availability

  validate :only_a_guide_has_years_in_business

  validate :only_a_guide_has_certified

  validate :only_a_guide_has_location

  def only_a_guide_has_bio
    if self.bio
        self.errors.add(:bio, "Only a guide can have a home.") unless self.guide?
    end
  end

  def only_a_guide_has_availability
    if self.availability
        self.errors.add(:availability, "Only a guide can have availability.") unless self.guide?
    end
  end

  def only_a_guide_has_years_in_business
    if self.years_in_business
        self.errors.add(:years_in_business, "Only a guide can have years in business.") unless self.guide?
    end
  end

  def only_a_guide_has_certified
    if self.certified
        self.errors.add(:certified, "Only a guide can be certified.") unless self.guide?
    end
  end

  def only_a_guide_has_location
    if self.location
        self.errors.add(:location, "Only a guide can have location.") unless self.guide?
    end
  end

  def first_name
    self.first_name
  end
  ####################################################################################

  def guide?
    self.type == 'Guide'
  end

  def client?
    self.type == 'Client'
  end

end

Here is my Guide model:

class Guide < User
    has_many :trips
end

Here is my Client model:

class Client < User
has_and_belongs_to_many :trips

 def new
     super
 end
end

Here is the User sessions controller:

class Users::SessionsController < Devise::SessionsController
  include ApplicationHelper

  def create
    super
  end
  def new
    super
  end

  def destroy
    super
  end
end

The super for devise create is:

  def create
    self.resource = warden.authenticate!(auth_options)
    set_flash_message(:notice, :signed_in) if is_flashing_format?
    sign_in(resource_name, resource)
    yield resource if block_given?
    respond_with resource, location: after_sign_in_path_for(resource)
  end

(more here https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb)

And this is the new session view:

.page-header
  %h1= t ".title", default: "Login"
= form_for(resource, url: new_user_session_path, html: {class: "form-horizontal form-user"}) do |f|
  .form-group
    = f.label :email, class: "control-label col-md-4"
    .col-md-8
      = f.email_field :email, class: "text-field form-control", html: {spellcheck: "false"}
  .form-group
    = f.label :password, class: "control-label col-md-4"
    .col-md-8
      = f.password_field :password, class: "text-field form-control", html: {autocomplete: "off"}
  - if devise_mapping.rememberable?
    .form-group
      = f.label :remember_me, "Remember Me", class: "control-label col-md-4"
      .col-md-8
        = f.check_box :remember_me
  .form-group
    .col-md-offset-4.col-md-8
      = f.submit "Login", class: "btn btn-primary"
  .form-group
    .col-md-offset-4.col-md-8
      = render "users/shared/links"
1

There are 1 answers

1
AudioBubble On BEST ANSWER

Changing SessionsController#create to the following works for me:

def create
  # We need to determine exactly which subclass the signed in user is and put
  # it in the auth_options hash. Also, we cannot modify auth_options, so we
  # make a copy of it, alter it, and pass it to warden.autenticate!. (You could
  # also merge the new scope into auth_options.)

  u = User.where(email: request.params[:user][:email]).take
  user_type = u.type.downcase.to_sym
  ao = auth_options
  ao[:scope] = user_type

  self.resource = warden.authenticate!(ao)
  set_flash_message(:success, :signed_in) if is_flashing_format?
  sign_in(resource_name, resource)
  yield resource if block_given?
  respond_with resource, location: after_sign_in_path_for(resource)
end

This assumes User is the superclass and that instances of both subclasses log in using their e-mail addresses. If they use different attributes, you can check for them by looking at request.params and appropriately modifying #create.

EDIT: I also needed to add controllers: { sessions: 'sessions' } to the relevant routes:

devise_for :users, controllers: { sessions: 'sessions' }
devise_for :admins, controllers: { sessions: 'sessions' }
# etc...

If this doesn't work, you may want to check out this thread for some ideas.