Carpela Carpela - 2 months ago 19
Ruby Question

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.

Answer

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.present?
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.present?
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