Search code examples
ruby-on-railsassociationscancan

Rails cancan not working properly


I am trying to build a blog where there will be 2 users admin and normal user.Admin can view every post and comment .Whereas normal user can view his only post and comment .I have applied my logic but it is not working .In my code every user can view each other post and comment which I don't want .I have uploaded my code at github
link


[ability.rb]

class Ability
  include CanCan::Ability
   def initialize(user)
        unless user
        else
          case user.roles
          when 'admin'
           can :manage, Post
           can :manage, Comment
         when 'user' 
           can :manage, Post, user_id: user.id
           can :manage, Comment, user_id: user.id
         end
        end


class PostsController < ApplicationController
    before_action :authenticate_user! 
    authorize_resource

    def index
        @posts = Post.all.order('created_at DESC')
    end

    def new
        @post = Post.new
    end

    def show
        @post = Post.find(params[:id])
    end

    def create
        @post = Post.new(post_params)
        @post.user = current_user

        if @post.save
            redirect_to @post
        else
            render 'new'
        end
    end

    def edit
        @post = Post.find(params[:id])
    end

    def update
        @post = Post.find(params[:id])

        if @post.update(params[:post].permit(:title, :body))
            redirect_to @post
        else
            render 'edit'
        end
    end

    def destroy
        @post = Post.find(params[:id])
        @post.destroy

        redirect_to posts_path
    end

    private

    def post_params
        params.require(:post).permit(:title, :body)
    end
end


[comment_controller]

class CommentsController < ApplicationController
          authorize_resource

    def create
        @post = Post.find(params[:post_id])
        @comment = @post.comments.build(params[:comment].permit(:name, :body))
         @comment.user = current_user
         @comment.save
        redirect_to post_path(@post)
    end

    def destroy
        @post = Post.find(params[:post_id])
        @comment = @post.comments.find(params[:id])
        @comment.destroy

        redirect_to post_path(@post)
    end
end


[user.rb]

class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
      has_many :posts
      has_many :comments
end


[post.rb]

class Post < ActiveRecord::Base
    has_many :comments, dependent: :destroy
    validates :title, presence: true, length: {minimum: 5}
    validates :body,  presence: true
    belongs_to :user
end


[comment.rb]

class Comment < ActiveRecord::Base
  belongs_to :post
  belongs_to :user

end

Solution

  • Firstly...

    CanCan is no longer maintained; CanCanCan should be added to your Gemfile:

    #Gemfile
    gem "cancancan"
    

    --

    You'll be able to use the following:

    #app/models/ability.rb
    class Ability
      include CanCan::Ability
    
      def initialize(user)
        user ||= User.new # guest user (not logged in)
        case user.roles
           when "admin" #-> use double quotes for evaluating strings
              can :manage, [Post, Comment]
           when "user"
              can :manage, [Post, Comment], user_id: user.id
           end
      end
    end
    

    --

    You also need to make sure you're calling authorize!.

    Although authorize_resource is good, in your case, you need to make sure you're sticking to convention...

    #app/controllers/comments_controller.rb
    class CommentsController < ApplicationController
        authorize_resource :post
        authorize_resource :comment
    
        def create
            @post    = Post.find params[:post_id]
            @comment = @post.comments.new comment_params
            @comment.user = current_user
            @comment.save
    
            redirect_to @post
        end
    
        def destroy
            @post    = Post.find(params[:post_id])
            @comment = @post.comments.find params[:id]
            @comment.destroy
    
            redirect_to @post
        end
    
        private
    
        def comment_params
           params.require(:comment).permit(:name, :body)
        end
    end