1

So what I would like to do is to do redirects based on the role of the current_user.

This is what I have:

  path = case current_user.roles.where(:name => "vendor")
    when :vendor
      dashboard_path
    when :guest
      home_path
    else
      home_path
  end

  redirect_to path     

I am using cancan and the only way to figure out the role of a user, that I know of, is to either do current_user.has_role? :admin or current_user.roles.where(:name => role_name).

Given those constraints (or tell me another way to figure out the role of a user) how do I get this case statement to work?

Edit 1

Assume that I am checking for multiple roles, not just the 2 I have here - could be 4 or 5.

Edit 2

To be clear, this is my current setup.

I am using Devise, CanCan & Rolify. Rolify allows a user to have multiple roles, but my application won't have that use case. A user will just have one role. They can either be a vendor, buyer, guest, superadmin.

If they are a vendor, they can only see the dashboard_path that belongs to them. They can't see any other vendor storefront that belongs to anyone else. They also should not be able to see products from other vendors. So, once they login, their root_path should be dashboard_path not home_path which is what every other role's root_path will be.

If they are a guest, they can see everything except the prices - I already have this logic working. I achieved this like this:

if user.has_role? :guest
    can :read, [Product, Vendor, Banner]
    cannot :view_prices, Product
end

Then in my view, I just did something like this:

<% if can? :view_prices, Product %>
    <div class="price pull-right">
      <%= number_to_currency(@product.price) %> ea
    </div>      
<% else %>
   <span class="no-price pull-right"><%= link_to "Log In To See Price", login_path %></span>
<% end %>

So, basically...my real goal is to try and change the root_path depending on the role the user has. I am basically trying to implement the answer on this question.

Community
  • 1
  • 1
marcamillion
  • 32,933
  • 55
  • 189
  • 380

2 Answers2

6

FINAL ANSWER (for earlier anwsers, see below)

If your users can only have one role, I'd say your current implementation is not exactly appropriate. However, if you really need to keep this implementation, you can do something like this :

class User < ActiveRecord::Base
  # this will return the name of the first (so the only one) 
  # role that your user has, or nil. 
  def role_name
    roles.first.try( :name ) 
  end
end  

so now your case statement would work :

path = case current_user.role_name
         when 'vendor' ; dashboard_path
         when 'guest'  ; home_path
         when 'foo'    ; bar_path
         else home_path
       end

I still encourage you to wrap your case statement in a helper for reusability and easier maintainance.

EARLIER ANSWER

I'm not sure i understand your question, but i think you don't need a case statement here :

redirect_to (current_user.has_role? :vendor ? dashboard_path : home_path)

another way is to push part of the responsability to the user class (or a presenter):

class User < ActiveRecord::Base
  def home_page
    # here some logic to return a symbol like :home or :dashboard,
    # depending on your roles implementation. Your User could even use
    # a state machine to do this, or have multiple decorators.
  end
end

and then with a helper

 def home_path_for( user )
   case user.home_page
     when :home      ; home_path
     when :dashboard ; dashboard_path
     when :foo       ; bar_path
     else home_path
   end
 end

FIRST EDIT

if your user can have multiple roles at a time, i'd say a case statement is not appropriate. case is a branching statement that is appropriate when you only have one and only one input and one and only one outcome out of a set of possible outcomes.

So you have to reduce your list of roles to an intermediate state, for instance :

 DASHBOARD_ROLES = [:vendor, :admin]
 CLIENT_ROLES    = [:client, :prospect]
 BASIC_ROLES     = [:logged_visitor]

 if (DASHBOARD_ROLES & user_roles).any?
   :admin_dashboard
 else if (CLIENT_ROLES & user_roles).any?
   :client_dashboard
 # additionnal, different logic is possible
 else if (BASIC_ROLES & user_roles).any? && user_logged_in? && user_roles.first != :prospect
   :logged_dashboard
 else
   :home
 end

this is a completely different kind of logic.

m_x
  • 12,357
  • 7
  • 46
  • 60
  • Well....there are multiple roles - not just a `:vendor`. So I need more than just a ternary redirect. Re: your helper, I am doing effectively that with a controller - which is why I am using this case. Your case in the helper won't necessarily help me because I need to check for multiple roles. – marcamillion Mar 15 '13 at 08:58
  • what i did here is to split the responsibilities : the user class determines what kind of home page is attributed to a particular user, and then the controller (helper) routes to the correct path. So it would be the responsibility of a user to check his own roles. I'd say a __case statement would rapidly become a hassle to implement such logic__, that's why i'm talking about state machines or decorators ; I recommend that you read about design patterns (many good books out there) because there are many ways to solve such problem in a much more maintainable way that with a "case". – m_x Mar 15 '13 at 10:09
  • I will definitely look into design patterns. For this case, a user can only have 1 role - not multiple roles. But, there can be multiple roles (i.e. vendor, buyer, guest, admin, superadmin, etc.). No one will have multiple roles. Makes sense? I am still struggling with understanding your approach - specifically the `home_path_for(user)` - even with the case where a user can have only 1 role. – marcamillion Mar 15 '13 at 11:01
  • sorry, i assumed that your user could have multiple roles (`user.roles` tricked me into it). It all depends on your implementation - in this case i'd say implement a method like `user#role` that would return a symbol or string, and then you could use it with the case statement. Currently in your system, as i understand it, `user#roles` may return 0, 1 or many `Role` objects, which is not coherent with the idea of a user only having one role. Perhaps you could post details about your implementation of roles for further help. – m_x Mar 15 '13 at 13:51
  • i think @SrikanthVenugopalan also thought that your user could have multiple roles, that's why he suggested the `active_role` idea which is more or less the same concept than my `home_page` method : let the user class choose which role should be used to determine what home page to redirect to. – m_x Mar 15 '13 at 13:55
  • I added more details about my implementation so you can see what I am trying to do, what I have, and where I am getting inspiration for my approach :) – marcamillion Mar 16 '13 at 03:45
  • Yes, I was under the impression that User-Role relation is many-many. And also that the number of roles could be dynamic (more roles being added later). Hence I suggested using a hash and avoid `if-else` or `switches`. – Srikanth Venugopalan Mar 17 '13 at 04:33
  • updated my answer. I still think your implementation is not really adapted to what you want to do. – m_x Mar 17 '13 at 11:41
  • Or put in hash: path_cases = { 'vendor' => dashboard_path, 'guest' => home_path, 'foo' => bar_path } then path = path_cases[curren_user.role_name] rescue home_path – CodeGroover Sep 07 '13 at 16:35
  • why use rescue ? you can provide a default value returned by the hash if the key / value pair does not exist. see Hash#fetch. – m_x Sep 09 '13 at 07:13
1

First of all, you probably need to address the case when user has multiple roles, if that is possible.

Assuming a user has one role (though we can add more conditions if need be) Can you consider a hash?

Something like -

path = {:vendor => dashboard_path, :guest => home_path} [current_user.active_role] || default_path

I have made some assumptions -

  1. current_user.active_role could be the current role of the user based on which you can redirect the answer.
  2. default_path is self explanatory.
Srikanth Venugopalan
  • 9,011
  • 3
  • 36
  • 76
  • This is a possibility, for sure. In theory a user can have multiple roles. So this `path` local variable would go in my method above, right? – marcamillion Mar 15 '13 at 09:00
  • I am struggling to see how this would work....how would this detect when a user has `x` role, to send them to the right path? It doesn't seem to do it, to me. – marcamillion Mar 15 '13 at 09:22
  • When a user has multiple role, there must be another parameter to choose which role to use for deciding the path? Either a role hierarchy, or user choosing his/her current role? This is the `active_role`. – Srikanth Venugopalan Mar 15 '13 at 10:09