How do I handle authentication with Devise when using multiple models in Rails 3.2 App
Asked Answered
P

3

12

I'm working on a Rails 3.2 app where I use Devise for authentication. I decided to try single table inheritance for managing user roles, but I quickly ran into a problem. I currently have three User models, User < ActiveRecord, Admin < User and Collaborator < User. Admin and Collaborator share most User columns, but they have slightly different behaviors and privileges. My models currently looks like this:

class User < ActiveRecord::Base

  devise :database_authenticatable, :registerable,
     :recoverable, :rememberable, :trackable, :validatable, :token_authenticatable

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

  before_save :ensure_authentication_token

  [...]

end

class Admin < User
  has_one :account, dependent: :destroy 
  attr_accessible :account_attributes 
  accepts_nested_attributes_for :account
end


class Collaborator < User
  has_one :account
end

class Account < ActiveRecord::Base
  attr_accessible :name
  validates_presence_of :name 
  has_many :projects, dependent: :destroy
  has_many :users
end

The problem aries when I try to authenticate Admins and Collaborators in my ProjectController (and other controllers where I need authentication):

# Causes problem, no one can access anything.
before_filter :authenticate_admin!
before_filter :authenticate_collaborator!

A similar problem I had was with devise's helper methods for ie. current_user, now I have current_admin and current_collaborator, I "solved" that by creating a before filter and method:

def set_current_user
  @current_user = current_admin || current_collaborator
end

Is there a similar or simple solution for my authentication problem with Devise, or would you recommend another approach than Single Table Inheritance, and what would that be?

My goal is, 1. when new users signs up, they become Admins, when they create their account, an Account model is also created. 2. The new (Admin) user can then invite additional users to the Account, which will be Collaborators. 3. Admins and Collaborators should have different privileges. Collaborators won't create new "Accounts" when they sign up (company could be a better name for my Account model) so Admin and Collaborators will need slightly different forms for signing up and editing.

Thanks.

Update

I kinda "solved" it by creating a similar before filter:

def authenticate!
  if @current_user == current_admin
    :authenticate_admin!
  elsif @current_user == current_collaborator
    :authenticate_collaborator!
  end
end

Suggestions on possibly more elegant solutions would still be appreciated.

Premedical answered 21/4, 2012 at 20:12 Comment(1)
get a chance to test my answer?Swacked
B
6

You can solve this using the following solution

def authenticate!
    if modelA_user_signed_in?
      @current_user = current_modelA
      true
    else
      authenticate_modelB!
    end

  end
Blockhead answered 17/12, 2013 at 19:23 Comment(0)
S
4

Not sure if this is still needed a solution to this...

A more elegant way to have the double authentications could be to do the following:

private

def authenticate!
   :authenticate_admin! || :authenticate_collaborator!
   @current_user = admin_signed_in? ? current_admin : current_collaborator
end

Then call before_filter :authenticate!

If you dont need a universal '@current_user' variable just leave out the second line.

Hope this helps.

Swacked answered 3/6, 2013 at 20:36 Comment(3)
This solution doesnt work because authenticate_admin will redirect if fails ... any ideas ? I can't get it to workBlockhead
you're right, in the newest versions this no longer works. I'll see if I can find a work around.Swacked
I added an answer with my solutionBlockhead
H
1

You may separate all common logic to module and use only same table.

module UserMethods
  #...
end

class User < ActiveRecord::Base
  include UserMethods
  devise ...

end  

class Admin < ActiveRecord::Base
  include UserMethods
  self.table_name = "users"
  devise ...
end

And configure all devise model separately in routes, views(if necessary, see Configuring Views). In this case, you may easy process all different logic.

Holifield answered 26/4, 2012 at 8:49 Comment(2)
Mm, thanks. Seems like that would work. However, how is this solution better than my current solution? Sorry for my newbish question. Here comes another one. I'm thinking of changing my logic slightly, and allow Users to have many Accounts, either be Admins or Collaborator for each account, a many-to-many relationship between User and Account, how could I achieve that with your or mine solution, and still have different roles? Thanks!Premedical
Instead of including module, you may extract all logic to common model, for example, BaseUser, place here all relationships. But all different logic, related to devise or attributes, you may place to separate model. I.e. it looks like your and my approach should be combined. I don't like 'if' conditions in methods, as it isn't OOP style.Holifield

© 2022 - 2024 — McMap. All rights reserved.