bgadoci
bgadoci

Reputation: 6493

CanCan, Rails, and Collaboration

I have a Ruby on Rails app with users, articles and collaborations. Here are the relationships:

User has_many :articles
User has_many :collaborations

Article belongs_to :users
Article has_many :collaborations

# Collaboration has collaboration.id collaboration.user_id and collaboration.article_id.
Collaboration belongs_to :users
Collaboration belongs_to :articles

I am successfully able to access both users and articles by going through collaborations so I think everything is set up in my app correctly. Ok on the to question.

I'm using CanCan with an role of :admin. Basically I only want :admin to be able to create posts and collaborations and I have that working correctly as well. The question is...how do I write that role into my ability.rb file such that users who are NOT admin can still collaborate on articles where they are part of a collaboration?"

  1. User A (who is an admin) creates an Article X
  2. User A creates a collaboration on Article X with User B
  3. User B logs in but is only able to edit & update Article X

How should I write that in ability.rb. It's like I want to say the following: "Users who aren't admins can manage Articles where they are part of a collaboration for that article."

Sorry for being verbose, haven't had my coffee yet :). Here is my ability.rb.

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user
    if user.role == "admin"
      can :manage, :all
    else
      can :read, Article
      # this is where I want to say: can :manage if part of collaboration for article 
    end
  end
end

Upvotes: 1

Views: 615

Answers (5)

Vikko
Vikko

Reputation: 1406

Here is a video guide by the author of CanCan that might clearify the correct usage of the gem.

http://railscasts.com/episodes/192-authorization-with-cancan

Dont forget to after setting all the permissions to authorize actions done by the current user in your controller. More documentation on github.

I used CanCan about a year ago and the it was easily set with the command load_and_authorize_resource

Class ArticlesController > ApplicationController
  load_and_authorize_resource #authorize all actions based on ability.rb

  def index
    ## manual authorization ##
    @articles = Article.all
    authorize! :read, @articles #Manually authorize this action. You dont need this if you put load_and_authorize_resource on top. 
    ## end manual authorization ##
  end

  def show 
    #@article = Article.find(params[:id]) #already loaded and authorized through load_and_authorize_resource
  end

end

Enjoy the coffee.

Upvotes: 0

vijikumar
vijikumar

Reputation: 1815

Try with the following method.

def initialize(user)

user ||= User.new
if user.admin?
    can :manage :all
elsif user.registered?
    can :manage, Article, :collaborations=>{:user_id=>user.id}
    can :read,  Article
else
    can :read,  Article
end

end

Upvotes: 0

Arihant Godha
Arihant Godha

Reputation: 2469

You can try something like this in your ability.rb file .Just define the role in your users table and use is as enum in your model file like

:role, ["Admin", "Guest", "Other"]

if user.role == "Admin"
      if user.role == "admin"
            can :manage, :all
      else
           can :read, Article
      end
end

Upvotes: 1

Prasad Surase
Prasad Surase

Reputation: 6574

try

can :manage, Article, :collaborations => {:user_id => user.id}

Upvotes: 0

rossta
rossta

Reputation: 11494

Here's logic in your Ability class that may work for you:

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user
    case
    when user.admin?
      can :manage, :all
    when user.registered?
      can :read, Article
      can :manage, Article, :collaborations => { :user_id => user.id }
    else # user.guest?
      can :read, Article
    end
  end
end

CanCan allows you to specify the conditions for associations; here, we pass the :user_id conditions for the :collaborations association on Article.

Additional methods added to user:

class User < ActiveRecord::Base

  ROLES = [
    ADMIN = 'admin'
  ]

  def admin?
    role == ADMIN
  end

  def registered?
    persisted?
  end

end

To be sure this is working correctly, here's how you might write the tests with RSpec, FactoryGirl and CanCan matchers:

require 'spec_helper'
require "cancan/matchers"

describe Ability do
  subject { Ability.new(user) }

  context "admin" do
    let(:user) { create(:admin) }

    it { should be_able_to(:manage, :all) }
  end

  context "user" do
    let(:user) { create(:user) }

    it { should be_able_to(:read, Article) }

    it "cannot manage articles without collaborations" do
      article = create(:article)
      should_not be_able_to(:manage, article)
    end

    it "cannot manage articles only others collaborated on" do
      article = create(:article)
      article.collaborations.create { |c| c.user = create(:user) }
      should_not be_able_to(:manage, article)
    end

    it "can manage article with collobaration" do
      article = create(:article)
      article.collaborations.create { |c| c.user = user }
      should be_able_to(:manage, article)
    end
  end

  context "guest" do
    let(:user) { User.new }

    it { should be_able_to(:read, Article) }
    it { should_not be_able_to(:manage, Article) }
  end
end

Upvotes: 4

Related Questions