I have models created as follows (self-connection in contacts, because the information I wanted to save for resellers reflected all the fields in this table, it seemed, according to DRY, to use existing data structures):
class Contact < ActiveRecord::Base attr_accessible :reseller_id has_and_belongs_to_many :users has_many :reseller_clients, :class_name => "Contact", :foreign_key => "reseller_id" belongs_to :reseller, :class_name => "Contact" end class User < ActiveRecord::Base attr:accessible :name has_and_belongs_to_many :contacts end
With cancan, I want to have a reseller who can manage their own contact. The mapping between users and resellers is HABTM, so you can do this by doing can :manage Contact, :users => {:id => user.id} , as shown below.
I also want the reseller’s login to be able to manage all the contacts that match the set described in the manage_account file in the following logic:
reseller_contacts = user.contacts managed_accounts = [] reseller_contacts.each do |i| managed_accounts << i.reseller_clients end managed_accounts.flatten!
My current skill class has:
class Ability include CanCan::Ability def initialize(user) if user.role? :reseller
The error that I get with it as is is as follows:
Mysql2::Error: Unknown column 'contacts.users' in 'where clause': SELECT `contacts`.* FROM `contacts` INNER JOIN `contacts` `resellers_contacts` ON `resellers_contacts`.`id` = `contacts`.`reseller_id` INNER JOIN `contacts_users` ON `contacts_users`.`contact_id` = `resellers_contacts`.`id` INNER JOIN `users` ON `users`.`id` = `contacts_users`.`user_id` INNER JOIN `contacts_users` `users_contacts_join` ON `users_contacts_join`.`contact_id` = `contacts`.`id` INNER JOIN `users` `users_contacts` ON `users_contacts`.`id` = `users_contacts_join`.`user_id` WHERE ((`contacts`.`users` = '---\n:id: 6\n') OR (`users`.`id` = 6))
My understanding of cancan is that it checks on the basis of contact what is and is not allowed. If I could do what I wanted in the block, it would look like this (it covers both the reseller’s own contact and all the contacts that are the reseller’s clients):
can :manage, Contact do |contact| user.contacts.exists?(contact.reseller_id) || user.contacts.exists?(contact.id) end
I can’t use the block for this, since trying to use @contacts = Contact.accessible_by(current_ability) in my index action on the controller, I get:
The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for :index Contact(id: integer, first_name: string, last_name: string, postal_addr_line_1: string, postal_addr_line_2: string, postal_addr_line_3: string, postal_addr_city: string, postal_addr_post_code: string, postal_addr_country: string, billing_addr_line_1: string, billing_addr_line_2: string, billing_addr_line_3: string, billing_addr_city: string, billing_addr_post_code: string, billing_addr_country: string, contact_email: string, company_name: string, phone_home: string, phone_work: string, phone_mobile: string, split_bills: boolean, created_at: datetime, updated_at: datetime, reseller_id: integer)
Edit:
ALMOST solved, now I just have the problem of combining abilities:
I changed the working part of my skill model as follows:
reseller_contacts = user.contacts managed_accounts = [] reseller_contacts.each do |i| i.reseller_clients.each do |rc| managed_accounts << rc.id end end can :manage, Contact, :id => managed_accounts can :manage, Contact, :users => {:id => user.id} can :create, Contact
Now the only problem is that the first line of can :manage will be overwritten by the second. I got the impression that they should be additive, not replace. More research is needed, but I think this question is reinforced above. Now I need to figure out how to use both can :manage strings.