3

I have built Ruby On Rails application using Devise + CanCanCan + rolify Tutorial.

Here is my Ability model:

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)
    if user.has_role? :admin
      can :manage, :all
    else
      can :read, :all
    end
  end
end

I want to allow user edit their own posts, and read posts by others.

How could I achieve that?

Community
  • 1
  • 1
pavjel
  • 486
  • 10
  • 25

2 Answers2

5

You just need to pass the user_id to the hash conditions:

#app/models/ability.rb
class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)
    if user.has_role? :admin
      can :manage, :all
    else
      can :manage, Post, user_id: user.id #-> CRUD own posts only
      can :read, :all #-> read everything
    end
  end
end

This would allow you to use:

#app/views/posts/index.html.erb
<%= render @posts %>

#app/views/posts/_post.html.erb
<% if can? :read, post %>
   <%= post.body %>
   <%= link_to "Edit", edit_post_path(post), if can? :edit, post %>
<% end %>
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • 1
    Thank you so much! Worked like a charm! – pavjel Jan 27 '16 at 15:52
  • Thanks man, I spent like 6 hours the other day on something similar. – Richard Peck Jan 27 '16 at 15:53
  • Well, I'm quite new to ruby and ruby on rails in a general, I don't know what would I do without a SO (Obviously, I'd read a docs, but it'd take too much time) :) – pavjel Jan 27 '16 at 15:56
  • Honestly, the docs are best for *reference*; if you want to achieve something specific, seeing what others have done is far more efficient – Richard Peck Jan 27 '16 at 20:03
  • It doesn't work in my case (Rails 6.0.3.2), part of link "if can? :edit, post" causes view to crash. But on another side, I don't know how to fix it so that it would work. Putting <% if can? :edit, Post %> before link makes "Edit" to appear when it shouldn't appear, and clicking on it gives "You are not authorized to access this page." – Clarity Aug 19 '20 at 13:19
3

I agree with Richard Peck's answer. However, I would just like to point out that catering for a guest user (not logged in) is not needed. An initializer is invoked on instantiation of a new object (i.e an object's constructor).

Thus, the above Ability class could be as follows:

#app/models/ability.rb
class Ability
 include CanCan::Ability

 def initialize(user)

  if user.has_role? :admin
    can :manage, :all
  else
    can :manage, Post, user_id: user.id #-> CRUD own posts only
    can :read, :all #-> read everything
  end
 end
end