Search code examples
ruby-on-railsauthorizationcancancancancan

CanCanCan gem: Handling the case where there is no logged in user


I'm using the CanCanCan gem (v.12.0) for basic authorization. My User model does not use the concept of roles. All I am trying to do is ensure a user is logged in before they can do anything with my main model, Topic.

I believe I've written my Ability class correctly. My TopicsController has an index method that, unsurprisingly, displays all of a user's topics.

The issue is that the first if in index.html.erb is succeeding when there is no logged in user - that doesn't make sense to me. Am I missing something obvious?

# ability.rb    
class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # default user

    # as long as a user is logged in and owns the topic it can do anything it likes with it
    can [:manage], Topic do |topic|
      !user.new_record? && topic.try(:user) == user
    end
end

--

# topics_controller.rb
class TopicsController < ApplicationController
  load_and_authorize_resource # CanCanCan gem

  def index
  end
  ...
end

--

# index.html.erb
<% if can? :read, Topic %>
  <% @topics.each do |topic| %>
    <%# Display all the topics %>
<% else %>
  <%# Handle the case where the user can't read Topics %>
<% end %>

Solution

  • You need to authorize the user for a particular instance:

    <%= if can? :read, topic %>
    

    When you attempt to authorize a user for an entire class, as you do above, CanCanCan ignores any conditions defined in the Ability because it can't determine a user relation for the entire Topic model; it can only do so for a single topic instance. Unfortunately, AFAIK there's no way to authorize for a relation, so you'll have to either authorize for each instance in the relation.

    Alternatively, in version 1.4, load_and_authorize_resource in the controller should initialize @topics based on the current_user only if the Ability is defined without blocks. However, its nearly trivial to do this "manually":

    @topics = current_user.topics