Dercni
Dercni

Reputation: 1224

Pundit authorizing current user

My policy for creating, updating & destroying "likes" requires the user to be logged in.

I have worded the policy as follows:

class LikePolicy < ApplicationPolicy
  def create?
    user && record.user_id == user.id
  end

  def update?
    create?
  end

  def destroy?
    create?
  end
end

My likes#create controller action is as follows:

def create
  @article = Article.find(params[:article_id])
  @like = @article.likes.new(user_id: current_user.id, value: params[:value] == 1 : 1 ? -1)
  authorize(@like)
  @like.save
  redirect_to @article
end

This works however using policy to confirm the user is logged in is illogical as the code will fail on the previous like where current_user is referenced.

Is this the accepted practice for how to authorize records that include user_id?

Upvotes: 4

Views: 5448

Answers (1)

Cluster
Cluster

Reputation: 5616

Normally I would handle checking that a user is logged in with a separate authentication check. If your application mostly requires a user to be signed in, then doing an authentication check before the authorization will ensure that you always have a current_user.

Also for creates I found I wasn't liking the idea of calling new/authorize/save just to make sure the user was creating an object that belonged to them. That's application logic that really has nothing to do with authorization. Instead for create you can simply pass in the class, Pundit doesn't mind.

Depending on whether you do the authentication check first, your authorize can simply return true, or you could check for !user.nil.

The last thing I've found helpful with Pundit is to try and abstract logic into somewhat self documenting methods. It makes reading pundit policies easier and also allows you to abstract pieces of logic up to the ApplicationPolicy or include them via modules. In the example below the is_owner? can easily be abstracted as it is something that is usable in many situations.

Example 1: Authenticate then Authorize

class LikesController
  # Either from devise, or define in ApplicationController
  before_action :authenticate_user!

  def create
    authorize(Like)
    article = Article.find(params[:article_id])
    article.likes.create(user_id: current_user.id, ...)
    redirect_to article
  end
end

class LikePolicy < ApplicationPolicy
  def create?
    true
  end

  def update?
    is_owner?
  end

  def destroy?
    is_owner?
  end

  private

  def is_owner?
    user == record.user
  end
end

Example 2: Authorize Only

class LikesController
  def create
    authorize(Like)
    article = Article.find(params[:article_id])
    article.likes.create(user_id: current_user.id, ...)
    redirect_to article
  end
end

class LikePolicy < ApplicationPolicy
  def create?
    !user.nil?
  end

  def update?
    is_owner?
  end

  def destroy?
    is_owner?
  end

  private

  def is_owner?
    user && user == record.user
  end
end

Upvotes: 6

Related Questions