12
votes

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.

3
get a chance to test my answer?blnc

3 Answers

6
votes

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
4
votes

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.

1
votes

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.