1

I am following this tutorial I am trying to authorize user only If user is admin he should be able to see all post and comments otherwise the normal user can see its own post only .I have read github page but was quite confusing


[post_controller.rb]

class PostsController < ApplicationController
    before_action :authenticate_user!, except: [:index, :show]

    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


[comments_controller]

class CommentsController < ApplicationController
    def create
        @post = Post.find(params[:post_id])
        @comment = @post.comments.create(params[:comment].permit(:name, :body))
              @comment.user = current_user

   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


[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' # or whatever role you assigned to a normal logged in user
            can :manage, Post, user_id: user.id
            can :manage, Comment, user_id: user.id
          end

   end


[comment.rb]

class Comment < ActiveRecord::Base
  belongs_to :post
end


[post.rb]

class Post < ActiveRecord::Base
    has_many :comments, dependent: :destroy
    validates :title, presence: true, length: {minimum: 5}
    validates :body,  presence: true
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
end


[migration]

class DeviseCreateUsers < ActiveRecord::Migration
  def change
    create_table(:users) do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip
      t.timestamps
    end
    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
   end
end


[migration]

class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.string :name
      t.text :body
      t.references :post, index: true

      t.timestamps
    end
  end
end


[migration]

class CreatePosts < ActiveRecord::Migration
  def change
    create_table :posts do |t|
      t.string :title
      t.text :body

      t.timestamps
    end
  end
end
Aniket Tiwari
  • 3,561
  • 4
  • 21
  • 61

4 Answers4

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

  def initialize(user)
      user ||= User.new # guest user (not logged in)
      case user.role
        when "admin"
           can :manage, :all
        else
           can :read, Post #-> cannot read comments
      end
  end
end

The above is how the ability class should look. You can replace the switch/case with if/else.

--

You're missing the evaluation of your objects, specifically with the can? & authorize methods:

#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
   def create
      @post = Post.find params[:post_id]
      @comment = @post.comments.new comment_params
      @comment.save if authorize! :create, @comment

      redirect_to @post
   end

   def destroy
     @post = Post.find params[:post_id]
     @comment = @post.comments.find params[:id]
     @comment.destroy if authorize! :destroy, @comment

     redirect_to @post
   end

   private

   def comment_params
      params.require(:comment).permit(:name, :body)
   end
end

#app/controllers/posts_controller.rb
class PostsController < ApplicationController
   def show
      @post = Post.find params[:id]
   end
end

#app/views/posts/show.html.erb
<%= @post.title %>
<%= render @post.comments if can? :read, @post.comments %>
Community
  • 1
  • 1
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
1

It seems you do not yet have a user relationship to post and comment in which you need in order to identify if the user owns/created the comment/post

Run:

rails generate migration AddUserToPost user:belongs_to
rails generate migration AddUserToComment user:belongs_to
bundle exec rake db:migrate

Then add the association relationships:

post.rb

class Post < ActiveRecord::Base
  belongs_to :user
  # ..
end

comment.rb

class Comment < ActiveRecord::Base
  belongs_to :user
  # ..
end

user.rb

class User < ActiveRecord::Base
  has_many :posts
  has_many :comments
  # ..
end

Now you can identify who owns the post/comment, and what posts/comments a user owned/created with something like the following pseudo-code:

# rails console
post = Post.find(1)
post_owner = post.user

comment = Comment.find(1)
comment_owner = comment.user

user = User.find(1)
user_comments = user.comments
user_posts = user.posts

Now, the next step is to auto-associate the logged-in user to newly created posts/comments. This is done through the controllers:

posts_controller.rb

class PostsController < ApplicationController
  authorize_resource
  # ..

  def create
    @post = Post.new(post_params)
    @post.user = current_user # I assume you have a variable current_user, or if you are using Devise current_user is already accessible

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

comments_controller.rb

class CommentsController < Application
  authorize_resource
  # ..

  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.build(params[:comment].permit(:name, :body))
        #puts "hhhhhhhhhh#{@comment}"
    @comment.user = current_user # I assume you have a variable current_user, or if you are using Devise current_user is already accessible

    @comment.save

    redirect_to post_path(@post)
  end 
end

Now, at this point. Whenever a post/comment gets created, the logged-in user is automatically associated to it (as the owner).

Finally, we could just update the Ability class to only authorize users to :edit, :update, :show, and :destroy actions, if the user_id: current_user (logged-in user).

ability.rb

class Ability
  include CanCan::Ability

  def initialize(user)
    # if not logged in (Guest)
    unless user
      # cant do anything unless you add more `can` here
    # else if logged in
    else
      case user.role
      when 'admin'
        can :manage, Post
        can :manage, Comment
      when 'normal' # or whatever role you assigned to a normal logged in user
        can :manage, Post, user_id: user.id
        can :manage, Comment, user_id: user.id
      # If you don't have a role name for a normal user, then use the else condition like Rich Peck's answer. Uncomment the following instead, and then comment the `when 'normal' block of code just above
      # else
      #   can :manage, Post, user_id: user.id
      #   can :manage, Comment, user_id: user.id
      end
    end
  end
end

Just a final helpful information to the Ability above:

  • can :manage, Post, user_id: user.id

    This is just a shorthand equal to:

    can [:show, :edit, :update, :destroy], Post, user_id: user.id
    can [:index, :new, :create], Post
    

    You will notice that user_id: user.id is not taken into consideration for :index, :new, and :create because these are :collection methods, and not :member methods. More info here

  • If you want readability and customizability, you may opt to use the longer one above instead of the shorthand :manage.

Community
  • 1
  • 1
Jay-Ar Polidario
  • 6,463
  • 14
  • 28
  • I need a user should be logged in and can see its only post and comment .... but admin can everyone post and comments – Aniket Tiwari Feb 01 '16 at 09:57
  • @aniket, I assumed that your Post and Comment models both have an attribute (or you could say a 'column') named `user_id`, in which you have a `belongs_to :user` in both `Post` and `Comment` models of yours. If you do not have these, then you need to add them first because that is the identifier you need to check if a user owns the post or comment. – Jay-Ar Polidario Feb 01 '16 at 11:43
  • @aniket, I updated my answer to assign a user to a Post or Comment being created. So you know who created them. This user will be checked in your `ability.rb` to be authorized only if the user created them. – Jay-Ar Polidario Feb 01 '16 at 11:49
  • sure, that will be more helpful. But you don't have to if you already have `user_id` column for both Post and Comment. – Jay-Ar Polidario Feb 01 '16 at 11:53
  • thx for ur help ...this is my last question and as i am new to rails @comment.user = current_user ...so what does ' user ' means over here???is it a column name in my users table or model name or some predefined function??? – Aniket Tiwari Feb 01 '16 at 14:33
  • @aniket, the `.user` is a hidden method in Post and Comment that is automatically created when you add `belongs_to :user` to that Model. So let's say I have a model `class Animal < ActiveRecord::Base \n belongs_to :afhajrerz`. Then, I can access `@animal.afhajrerz` which is gonna be an instance of `class Afhajrerz < ActiveRecord::Base`. Same goes for `has_many` relationships. i.e. `class Group < ActiveRecord::Base \n has_many :users`, then I can access `@group.users` – Jay-Ar Polidario Feb 01 '16 at 14:45
  • @aniket, You can also still access `.user_id`, but it will return the `id` of the User instead of when accessing `.user` which returns the User object itself. You can treat as `.user_id` as a column/attribute just like any other attributes you may have (such as `name`, or `phone_number`), and you can treat `.user` however as a Record object. (i.e. for fun, you can even try this: `@post.user.posts.first.user.posts.first.user.posts.first.user.posts.first.user.posts.first`. It just shows that Record objects are different than attributes – Jay-Ar Polidario Feb 01 '16 at 14:56
0

1) Change this line in PostsController, delete this condition: except [index, show]. Or user could see pages without authorization.

before_action :authenticate_user!

2) Change index action and other with this style. Use - current_user.

def index
  if current_user.has_role? :admin        
    @posts = Post.all.order('created_at DESC')
  else
    @posts = current_user.posts.order('created_at DESC')
  end
end
Victor
  • 29
  • 8
0

You can write you abilities in this way

class Ability
  include CanCan::Ability

  def initialize(user)
      user ||= User.new # guest user (not logged in)
      case user.role
        when "admin"
           can :manage, :all
        else
           can :read, Post, :user_id => user.id
      end
  end
end

And just load resources of post using ability resource so that it only load post of current user if other than admin

class CommentsController < Application
  load_and_authorize_resource
    def index
     @posts = @posts
    end
end
Vishal JAIN
  • 1,940
  • 1
  • 11
  • 14