Search code examples
ruby-on-railscancancancancanruby-on-rails-5.2

cannot :destroy if last existing


The Setup

I'm using Rails 5.2 and CanCanCan.

rails g scaffold Hotel name
rails g scaffold PriceGroup name hotel:references

hotel.rb

has_many :price_groups, dependent: :destroy
validates :price_groups, :presence => true

ability.rb

if user.admin?
  can :manage, :all
else
  can :read, :all
end

The Challenge

I want to make sure that a Hotel always has at least one PriceGroup.

How can I configure cancancan to allow an admin to destroy a PriceGroup only if self.hotel.price_groups.count > 1?

I want to use CanCanCan tools to just display a delete button on the WebGUI when possible.


Solution

  • What @meta said is right, you shouldn't be adding your business logic to the ability . Instead you can override the existing destroy action in your PriceGroup model.

    That makes your logic universal (meaning, even a code outside the CanCan cannot delete the last object).

    An example would be

    class PriceGroup < ApplicationRecord
    
      def destroyable?
        PriceGroup.where(hotel_id: hotel_id).count > 1
      end
    
      def destroy
        return super if destroyable?
        raise "You cant delete the last price group of hotel #{hotel_id}"
      end 
    end
    

    Of course you can make the code more prettier , but you get the idea :)

    update

    Adding CanCan ability based on my above example

    According to the document here, you may try something like

    can(:delete, PriceGroup) { |price_group| price_group.destroyable? }