Search code examples
ruby-on-railsrubymetaprogrammingspree

Howto add a new role with a custom permission set for Solidus / Spree?


I want to add a new 'vendor' role. A vendor can manage his own products and orders, but not see any orders or products belonging to an other vendor.

I was planning to implement this around the Stock Location. E.g: One user with the vendor role belongs to the Tokyo stock location. An other user with the vendor role belongs to an other stock location.

Which steps need to me taken to get this working?


Solution

  • 1. Create a vendor role

    $ rails console
    Spree::Role.create(name: 'vendor')
    

    2. Define CanCan permissions:

    app/models/spree/multi_vendor_ability.rb

    module Spree
      class MultiVendorAbility
        include CanCan::Ability
    
        def initialize(user)
          user ||= Spree::User.new # guest user (not logged in)
          if user.admin?
            can :manage, :all
            puts "IS admin"
          else
            can :read, :all
            puts "NOT admin"
          end
    
          if user.stock_locations.present?
            can  :manage, Spree::StockItem, :stock_location_id => user.stock_locations.first.id
          end
        end
    
      end
    end
    

    3. Add a new method to Spree::User

    config/initializers/spree_user.rb

    Spree::User.class_eval do
    
        def vendor?
          self.role_users.any? { |ru| ru.role.name == 'vendor' }
        end
    
    end
    

    4. RSpec

    spec/models/multi_vendor_spec.rb

    require 'spec_helper'
    
    describe Spree::MultiVendorAbility do
      let(:ability) { Spree::MultiVendorAbility.new(user) }
      let(:user)    { create(:user) }
      let(:product) { create :product }
      let(:stock_location) { create(:stock_location_with_items) }
      let(:stock_item) { stock_location.stock_items.order(:id).first }
    
      subject { ability }
    
      context "test" do
        before do
          Rails.logger.debug "The StockItem is #{stock_item.inspect}"
          user.stock_locations = [stock_location]
          user.save
        end
    
        context "when the user is associated with the stock location" do
           it { is_expected.to be_able_to(:manage,  Spree::StockItem) }
        end
    
        context "when the user is NOT associated with the stock location" do
            before do
              stock_item.stock_location_id = nil
              stock_item.save
              user.stock_locations = []
              user.save
              puts "The StockItem is #{stock_item.inspect}"
              user.stock_locations = []
            end
            it { is_expected.to_not be_able_to(:manage, Spree::StockItem) }
        end
    
      end
    
    end