ruby-on-railscancancancancan

How to authorize namespace, model-less controllers using CanCanCan?


What is the correct way to authorize and check abilities for a namespaced, model-less controller using CanCanCan?

After much googling and reading the wiki, I currently have

#controllers/namespaces/unattacheds_controller.rb
def Namespaces::UnattachedsController
  authorize_resource class: false
  def create 
    # does some stuff
  end
end

#models/ability.rb
def admin 
  can [:create], :namespaces_unattacheds
end

#view/
<%= if can? :create, :namespaces_unattacheds %>
# show a create form to authorized users
<% end %>

This is not correctly authorizing the controller. Admins can see the conditional create form, but are not authorized to post to the create action.

post :create, valid_params
Failure/Error: { it { expect( flash ).to have_content "Successfully created" } 
expected to find text "Successfully created"
got: "You are not authorized to access this page."

In one example, the wiki suggests creating a separate Ability class for a namespaced controller. https://github.com/CanCanCommunity/cancancan/wiki/Admin-Namespace

Is there a simpler way to achieve this? This app uses many namespaced controllers, I don't really want to create an ability class for each one.

Is there correct syntax to refer to the namespaced controller in the Ability class?

can [:create], Namespaces::Unattacheds
can [:create], :namespaces_unattacheds
can [:create], namespaces/unattacheds
????

Solution

  • Adding an answer because this question seems to be getting a reasonable number of visitors. Hope it can help someone.

    I received some good suggestions here, that helped me think through my problem. Ultimately I realised that I was fighting against CanCanCan's conventions with what I was trying to achieve.

    I swapped to Pundit, which allowed me to authorise an object in the controller while referring to a specific policy class. Something like

    # Admin::UserController
    def update
    authorize @user, policy_class: Admin::UserPolicy
    end
    
    # UserController
    def update 
    authorize @user, policy_class: UserPolicy
    end
    

    Combined with strong params defined on the controller, I was able to ensure that only specific attributes could be updated.

    This won't work for everyone. For example, this approach would not prevent admins from updating all a user's attributes if they bypass the controller (e.g., via the console). But it worked for my situation.