Rails - CanCan HABTM Link Test

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 # Allow resellers to manage their own Contact can :manage, Contact, :users => {:id => user.id} # This works correctly at present # Allow resellers to manage their client Contacts can :manage, Contact, :reseller => {:users => {:id => user.id}} #This doesn't work end end end 

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.

+4
source share
2 answers

Edited 2015-03-26

Noticing that this question / answer received a bit of attention, I thought I should indicate the best method I have found since then.

When creating has_one / has_many associations, rails creates the foreign_model_id / foreign_model_ids respectively. These methods return an integer or an array of integers, respectively.

This means that instead of the answer below, the entry in the ability.rb file can be simplified without having to use this ugly logic to create my own array of objects and repeat them through:

can :manage, Contact, id: (user.contact_ids + user.reseller_client_ids)

Previous answer for posterity

Fixed using this in my Ability.rb file:

 # Manage all contacts associated to this reseller reseller_contacts = user.contacts managed_contacts = [] reseller_contacts.each do |i| i.reseller_clients.each do |rc| managed_contacts << rc.id end managed_contacts << i.id end can :manage, Contact, :id => managed_contacts 

Deefour, thanks for your help along the way, I don't think I would get there without your comments.

+8
source

I think you are still not formulating your request as clearly as you can.

... reseller ID. Contact

:reseller a Contact is another Contact . Contact no attribute :contact . You may be confusing things by referring to the “intermediary role” and “reseller” when you must refer to a user (from the CanCan class) to avoid confusion with the Contact :reseller class).

I suppose that

Reseller role for managing all contacts for which the reseller_id field is set to the identifier of the reseller’s own contact.

means

user can manage Contact c , where c.reseller_id is the user_id some contacts in user.contacts

Assuming this is an accurate interpretation:

 can :manage, Contact do |c| user.contacts.where(:user_id => c.reseller_id) end 
+1
source

All Articles