Skip to content

Account User scenario

kristianmandrup edited this page May 10, 2011 · 1 revision

In some cases you might have Accounts set up for different sub domains of the site. A User might then have certain permissions when looked into some parts of the site and other permissions in other parts of the site.

“The scenario I’m talking about is for applications where users create an account (usually with a subdomain) with their own members like lighthouse or basecamp. So a user would be an admin in the account they created but maybe a guest or an editor under someone else’s account. Systems like ACL9 account for this by allowing you to add role assignments to objects. So User A can be an admin on Account A and a guest on Account B.” – aaronchi

One way to set this up would look like this:


class Account < ActiveRecord::Base
  has_many :roles
  has_many :users, :through => :roles
end

class User < ActiveRecord::Base
  has_many :roles
  has_many :accounts, :through => :roles
end

class Role < ActiveRecord::Base
  belongs_to :account
  belongs_to :user
end

As I see it, what is implied is that the role should not be retrieved directly from the user, but should go through the account of the user. In cream, the default is to apply the role logic directly on the #current_user returned by #role_subject.

We could change the #role_subject method like this:


  def role_subject
    current_account.roles_of(current_user)
  end

Which would retrieve the roles (from joint Role table) for the current user for the account he is currently operating in.

In order to achieve this, you will have to customize the #role_subject and likely also the #post_signin and #post_signout methods, currently all defined in user_control.rb in the Cream source. You would likely have to assign and store the current account in the session and add a custom #current_account method that uses this session variable to retrieve the Account. This might be implemented something like the following:


    # Sign in an user that already was authenticated. This helper is useful for logging
    # users in after sign up.
    #
    # Examples:
    #
    #   sign_in :user, @user                      # sign_in(scope, resource)
    #   sign_in @user                             # sign_in(resource)
    #   sign_in @user, :account => :forum         # sign_in(resource, options)
    #
    def sign_in(resource_or_scope, *args)
      ...
    end

    def post_signin resource, options = {}
      session[:user_id] = resource.id
      session[:user_class_name] = resource.class.name
      # custom account logic
      session[:account_name] = options[:account]
    end

    def post_signout
      session[:user_id] = nil
      @current_user = nil      
    end

    def current_account
      Account.by_name(account_name)
    end

    protected

    def account_name
      session[:account_name]
    end

For sign_in you could use the options argument to set which Account the User signed in on and then store this info in the session and/or set on the User. Then the method #role_subject should return the Role instance retrieved by calling

current_account.roles_of(current_user)

Add a #has_role? method to the Role class and ensure that any cream methods such as #can? and #cannot? work on the #role_subject and not the #current_user. This should mostly do the trick I think (Not yet tested, May 10 – 2011).


class Role < ...
  ...

  def has_role? role_name
    self.name.to_sym == role_name.to_sym
  end  
end

Note that if the #roles_of call returns a collection of Roles, this collection must have the #has_role? and similar methods implemented (see the https://github.com/kristianmandrup/roles_generic gem)

Note that the #current_ability method is used for applying CanCan logic uses the #role_subject method.

Ability controller in Cream (ability.rb)


module Cream::Controller
  module Ability
    def current_ability
      @current_ability ||= Permits::Ability.new(role_subject, request)
    end
  end
end