2
votes

I'm trying to modify spree's admin/order site with cancan. At my shop products are sold from different vendors. Registered users have a vendor_id and products also have a vendor_id. When a order gets placed, a LineItem from the order has a Product and therefore it has a vendor_id.

What I want to achieve is, that when a user logs in and accesses the orders page in the admin panel, he can only see the orders where products from him are ordered. The user shouldn't see other orders from other vendors/users.

I tried to do this with CanCan by setting up some abilities:

  def initialize(user)
    if user.has_spree_role?("shop_manager") && !user.vendor.nil?
      can :admin, Spree::Order
      can :index, Spree::Order, :line_items => { :product => { :vendor_id => user.vendor_id } }
      can :show, Spree::Order, :line_items => { :product => { :vendor_id => user.vendor_id } }
      can :manage, Spree::LineItem, :product => { :vendor_id => user.vendor_id }
      can :read, Spree::Order
    end
  end

As you can see, a user should only see orders at the index page where :line_items => { :product => { :vendor_id => user.vendor_id } } and should only see LineItems where :product => { :vendor_id => user.vendor_id }.

But the behaviour that I get is, that I'm not authorized to see the index page of orders, and every LineItem is shown at the show action of an order.

For the second task, I thought that modifying the view would be a good idea. I tried to change the _order_details.html.erb from the spree core and changed the loop where all LineItems are displayed:

<% @order.line_items.accessible_by(@current_ability).each do |item| %>

But this gives me a Uninitialized constant Product (I think it should be Spree::Product). So, I don't know what to do, wether for the index nor for the show action with the LineItems. Hope someone has an idea.

2

2 Answers

1
votes

Why you set restriction for :read, :show and :index at the same time?

:read assumes :index and :show and any aliased methods.

Then, if you pass associated conditions, you have to :join or :include it.

def initialize(user)
  if user.has_spree_role?("shop_manager") && !user.vendor.nil?
    can :admin, Spree::Order
    can :read, Spree::Order, Spree::Order.includes(:line_items).where(:line_items => { :product => { :vendor_id => user.vendor_id } })
    can :manage, Spree::LineItem, Spree::LineItem.includes(:product).where(:product => { :vendor_id => user.vendor_id })
  end
end

For the second issue, try it again, but it will be much better if you collect records anywhere BEFORE ActionView runtime, in controller. It will be much much faster anyway.

Update:

def initialize(user)
  if user.has_spree_role?("shop_manager") && !user.vendor.nil?
    can :admin, Spree::Order
    can :read, Spree::Order, Spree::Order.includes(:line_items).where(:line_items => { :product => { :vendor_id => user.vendor_id } }) do |order|
      order.line_items.includes(:product).exists?(:vendor_id => user.vendor_id)
    end
    can :manage, Spree::LineItem, Spree::LineItem.includes(:product).where(:product => { :vendor_id => user.vendor_id })
  end
end

What pass the block. When you define :read, the first AR::Relation statement filters the :index (listing), and the block clause filters the :show and give you ability to user can? and cannot?.

1
votes

To resolve the Uninitialized constant Product issue, you need to add the :class option in the load_resource or load_and_authorized_resource before filter:

https://github.com/ryanb/cancan/wiki/authorizing-controller-actions