Using the CanCan and Role gems, what is still needed is a way to Check The Route and see if "current_user" has permissions to access that Route based on their role(s) - then show/hide based on that.
This saves the user clicking on things and getting told they cannot see it - or us having to write per-item "if" logic specifying what roles can see what list-items (which the customer will change periodically, as roles are changed/refined) around every single link in one's menu (consider a bootstrap menu with 50+ items nested in groups with html formatting, etc), which is insane.
If we must put if-logic around each menu-item, let's use the exact same logic for every item by checking the role/permissions we already defined in the Ability file.
But in our menu-list, we have route-helpers - not "controller/method" info, so how to test the user's ability to hit the controller-action specified for the "path" in each link?
To get the controller and method (action) of a path (my examples use the 'users_path' route-helper) ...
Rails.application.routes.recognize_path(app.users_path)
=> {:controller=>"users", :action=>"index"}
Get just the controller-name
Rails.application.routes.recognize_path(app.users_path)[:controller]
=> "users"
Ability uses the Model for its breakdown, so convert from controller name to it's model (assuming default naming used) ...
Rails.application.routes.recognize_path(app.users_path)[:controller].classify
=> "User"
Get just the action-name
Rails.application.routes.recognize_path(app.users_path)[:action]
=> "index"
And since the "can?" method needs a Symbol for the action, and Constant for the model, for each menu-item we get this:
path_hash = Rails.application.routes.recognize_path(app.users_path)
model = path_hash[:controller].classify.constantize
action = path_hash[:action].to_sym
Then use our existing Abilty system to check if the current_user can access it, we have to pass the action as a symbol and the Model as a constant, so ...
<% if can? action model %>
<%= link_to "Users List", users_path %>
<% end %>
Now we can change who can see this resource and link from the Ability file, without ever messing with the menu, again. But to make this a bit cleaner, I extracted out the lookup for each menu-item with this in the app-controller:
def get_path_parts(path)
path_hash = Rails.application.routes.recognize_path(path)
model_name = path_hash[:controller].classify.constantize
action_name = path_hash[:action].to_sym
return [model_name, action_name]
end
helper_method :get_path_parts
... so I could do this in the view (I took out all the html-formatting from the links for simplicity, here):
<% path_parts = get_path_parts(users_path); if can?(path_parts[1], path_parts[0]) %>
<%= link_to "Users Listing", users_path %>
<% end %>
... and to make this not take all day typing these per-menu-item if-wraps, I used regex find/replace with capture and wildcards to wrap this around every list-item in the menu-item listing in one pass.
It's far from ideal, and I could do a lot more to make it much better, but I don't have spare-time to write the rest of this missing-piece of the Role/CanCan system. I hope this part helps someone out.