Julian J. Tejera
Julian J. Tejera

Reputation: 1021

Cancan Authorization Forum

I'm working on a project using RoR, a social message board (internet forum), in which every user can create multiple Boards and join multiple Boards from other users.

I didn't want to reinvent the wheel so I'm using Devise for Authentication and CanCan for Authorization. However I'm having some issues implementing CanCan because of the following:

class Board < ActiveRecord::Base
  has_many :memberships
  has_many :users , :through => :memberships
end

class User < ActiveRecord::Base
  has_many :memberships
  has_many :boards, :through => :memberships
end

class Membership < ActiveRecord::Base
  ROLE = ['Administrator', 'Moderator','Member', 'Banned']
  belongs_to :user
  belongs_to :board
end

The role doesn't belong to the user himself, it belongs to the relationship between the user and the board, that is the Membership. So it's not enough knowing who is the current_user I also need to know which board is being displayed, so I think I would have to send the Membership instead of the user to the Ability class initializer? Any guidance would be greatly appreciated.

Upvotes: 1

Views: 134

Answers (1)

crftr
crftr

Reputation: 8526

You're on the right path.

If you haven't already, create this as an entirely new Ability. e.g. BoardAbility. I've found it useful to not be shy about passing-in additional dependencies, and to have CanCan do as much of the evaluation that's reasonable.

class BoardAbility
  include CanCan::Ability

  attr_reader :requested_by, :requested_resource

  def initialize requested_by, requested_resource
    return nil unless (requested_by.is_a?(User) && requested_resource.is_a?(Board))

    @requested_by       = requested_by
    @requested_resource = requested_resource

    default_rules
  end

  private

  def default_rules
    # common abilities to all users
    can :flag_offensive,    :all
    can :view_thread_count, :all

    # find this user's role to this board to define more abilities
    role = Membership.where(user_id: requested_by.id, board_id: requested_resource.id).pluck(:role).first

    if ['Administrator', 'Moderator'].include? role
      can :ban_users, Board, {id: requested_resource.id}
    end
  end
end

Then in your BoardController define a private method to signify that we aren't using the default CanCan Ability class.

def current_ability
  @current_ability ||= BoardAbility.new(current_user, @board)
end

Then when you're in your BoardController, use the usual CanCan DSL.

authorize! :ban_user, @board

Upvotes: 2

Related Questions