Search code examples
ruby-on-railscancancancancan

CanCan: Check permission on record including dependency


Example model

  • Users are in 0..* car pools
  • Each car is in 1 car pool
  • Each car pickup has 1 car

Permission setup

can :read, Car, :car_pool => { :users => { :id => user.id } }
can :create, CarPickup

Use Case

The goal is to let the user create a new CarPickup for one of his cars. The view could look something along the lines of:

= form_for @car_pickup do
  = f.association :car, :collection => Car.accessible_by(current_ability)

Question

When saving the user, it should be validated that the current user has access to the car passed with above form. Is there an elegant way of doing something along the lines of:

def create
  @car_pickup = CarPickup.new(params[:car_pickup])

  authorize :create, @car_pickup

  if @car_pickup.car
    Car.accessible_by(current_ability).include?(@car_pickup.car)
  end

  if @car_pickup.save
  # ...
end

Solution attempts / further questions

  • Adapt abilities like so:

    can :read, Car, :car_pool => { :users => { :id => user.id } }
    can :create, CarPickup, :car => { :car_pool => { :users => { :id => user.id } } }
    

    This breaks if the form is saved without selecting a product. For this solution, there would need to be a way to specify if not nil.

  • A second authorization call:

    authorize! :read, @car_pickup.car if @car_pickup.car
    
  • Can can?(:read, @car_pickup) be understood as the accessible_by for a single record?

  • Could this probably be achieved using load_resource?


Solution

  • Just found the solution, which is quite simple:

    can :read, Car, :car_pool => { :users => { :id => user.id } }
    can :create, CarPickup, :car => { :car_pool => { :users => { :id => user.id } }
    
    # Add this line in order for it to work when trying to save a
    # `CarPickup` without any `Car`.
    can :create, CarPickup, :car => nil