Search code examples
ruby-on-railsrubycancan

Allowing access cancan / cancancan when id is not nil


I'm trying to give access where an association exists using cancan.

It needs to work with both object attributes and accessible_by selection (so I can't use a block, unless someone tells me how I get around the error when I do that).

I've come up with a brutal hack.

lead.rb
has_many :recordings
has_many :emails

ability.rb

can :manage, Lead
can :manage, Recording, :lead_id => (1..Lead.pluck(:id).max).to_a
can :manage, Email, :lead_id => (1..Lead.pluck(:id).max).to_a

What I mean is lead_id is not null...

Is there any way I can do that without creating a 100,000 item array every time?


Extra info: It's worth noting that these are not the only controls in place on the Recording and Emails models, so it's important that I can add the extra permissions rather than reset and express them negatively.


Solution

  • There are two ways to achieve this:

    1. A combined hash of abilities

    This is the recommended approach. Use this, unless you have a good reason not to.

    The idea here is to combine a cannot ability, in conjunction with can:

    can :manage, Recording
    cannot :manage, Recording, lead_id: nil
    

    Note that the order is important here, so that the rule overriding is correct.

    2. A block of abilities

    By defining the ability with a block, you can form more complicated queries.

    A simple implementation would be as follows:

    can :manage, Recording do |recording|
      !recording.lead_id.nil?
    end
    

    However, in order to combine this ability with others, you must also specify the SQL conditions when fetching records. This additional SQL controls load_resource actions, such as index:

    can :manage, Recording, ["lead_id IS NOT NULL"] do |recording|
      !recording.lead_id.nil?
    end
    

    In order to keep this logic DRY, you could also consider defining your permissions in a block such as:

    [Recording, Email].each do |model|
      can :manage, model
      cannot :manage, model, lead_id: nil
    end