I am making a web app (chat sort of thing) since yesterday I switched from Pundit (as it was too difficult) to Cancancan (it looked better for me).
I am trying to make something simple to work such as displaying all Articles and its option (show, edit, destroy) and then setting permission on it so the only user that created such article will be able to edit or destroy it.
The problem is that I don't understand how it meant to be implemented fully. Google is lacking in examples and examples that are there are mostly outdated.
Here is what I have:
Ability.rb - I have no idea if this is even correct
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, :all
end
can :read, :articles
can :create, :articles
end
end
User.rb (Devise)
class User
include Mongoid::Document
has_many :articles
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
## Database authenticatable
field :username, type: String, default: ""
field :email, type: String, default: ""
field :encrypted_password, type: String, default: ""
## Recoverable
field :reset_password_token, type: String
field :reset_password_sent_at, type: Time
## Rememberable
field :remember_created_at, type: Time
## Trackable
field :sign_in_count, type: Integer, default: 0
field :current_sign_in_at, type: Time
field :last_sign_in_at, type: Time
field :current_sign_in_ip, type: String
field :last_sign_in_ip, type: String
## Admin
field :admin, :type => Boolean, :default => false
end
Article.rb
class Article
include Mongoid::Document
belongs_to :user
field :title, type: String
field :content, type: String
default_scope -> { order(created_at: :desc) }
end
index.html (displaying articles - only part where I added Cancancan)
<tbody>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.content %></td>
<td><%= link_to 'Show', article %></td>
<td>
<% if can? :update, @article %>
<%= link_to 'Edit', edit_article_path(article) %>
<% end %>
</td>
<td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
You need to define your authority by class in your Ability
file:
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, :all
else
can :read, :all
end
can [:credit, :edit, :update, :destroy], Article, user_id: user.id
end
end
--
#app/views/articles/index.html.erb
<tbody>
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.content %></td>
<td><%= link_to 'Show', article %></td>
<td><%= link_to 'Edit', article if can? :update, article %></td>
<td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } if can? :destroy, article %></td>
</tr>
<% end %>
</tbody>
As an aside, the second important factor to consider with this is that Devise
= authentication; CanCanCan
= authorization:
- Authentication = is user logged in?
- Authorization = can user do this?
I see a lot of people posting about "authorizing" with Devise
, when it's completely false. Devise
only handles authentication (user logged in?); when dealing with authorization, you need to work with a different pattern, harnessing the user
object Devise created.
Just wanted to point that out, considering you mentioned Devise
in your original post.