Search code examples
ruby-on-railsdevisepundit

Rails 4 with Pundit


I am trying to make an app in Rails 4.

I want to use Pundit for authorisations. I also use Devise for authentication and Rolify for role management.

I have a user model and am making my first policy, following along with this tutorial:

https://www.youtube.com/watch?v=qruGD_8ry7k

I have a users controller with:

    class UsersController < ApplicationController

before_action :set_user, only: [:index, :show, :edit, :update, :destroy]

  def index
    if params[:approved] == "false"
      @users = User.find_all_by_approved(false)
    else
      @users = User.all
    end

  end

  # GET /users/:id.:format
  def show
    # authorize! :read, @user
  end

  # GET /users/:id/edit
  def edit
    # authorize! :update, @user
  end

  # PATCH/PUT /users/:id.:format
  def update
    # authorize! :update, @user
    respond_to do |format|
      if @user.update(user_params)
        sign_in(@user == current_user ? @user : current_user, :bypass => true)
        format.html { redirect_to @user, notice: 'Your profile was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  # GET/PATCH /users/:id/finish_signup
  def finish_signup
    # authorize! :update, @user 
    if request.patch? && params[:user] #&& params[:user][:email]
      if @user.update(user_params)
        @user.skip_reconfirmation!
        sign_in(@user, :bypass => true)
        redirect_to @user, notice: 'Your profile was successfully updated.'
      else
        @show_errors = true
      end
    end
  end

  # DELETE /users/:id.:format
  def destroy
    # authorize! :delete, @user
    @user.destroy
    respond_to do |format|
      format.html { redirect_to root_url }
      format.json { head :no_content }
    end
  end

  private
    def set_user
      @user = User.find(params[:id])
    end

    def user_params
      params.require(:user).permit(policy(@user).permitted_attributes)
      # accessible = [ :first_name, :last_name, :email ] # extend with your own params
      # accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
      # accessible << [:approved] if user.admin
      # params.require(:user).permit(accessible)
    end

end

And this is my first go at the User policy.

class UserPolicy < ApplicationPolicy


  def initialize(current_user, user)
    @current_user = current_user
    @user = user

  end

  def index?
    @current_user.admin? 
  end

  def show?
    @current_user.admin? 
  end

  def edit?
    @current_user.admin? 
  end

  def update?
    @current_user.admin?  
  end

  def finish_signup?
    @current_user = @user
  end

  def destroy?
    return false if @current_user == @user
    @current_user.admin? 
  end

private
  def permitted_attributes
      accessible = [ :first_name, :last_name, :email ] # extend with your own params
      accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
      accessible << [:approved] if user.admin
      params.require(:user).permit(accessible)
  end

end

My questions are:

  1. The tutorial shows something called attr_reader. I have started learning rails from rails 4 so I don't know what these words mean. I think it has something to do with the old way of whitelisting user params in the controller, so I think I don't need to include this in my user policy. Is that correct?

  2. is it right that i have to initialise the user model the way I have above (or is that only the case in models other than user, since I'm initialising current_user, it might already get the user initialised?

  3. is it necessary to move the strong params to the policy, or will this work if I leave them in the controller?


Solution

    1. The tutorial shows something called attr_reader. I have started learning rails from rails 4 so I don't know what these words mean. I think it has something to do with the old way of whitelisting user params in the controller, so I think I don't need to include this in my user policy. Is that correct?

    No, it is very important.

    attr_reader creates instance variables and corresponding methods that return the value of each instance variable. - From Ruby Official Documentation

    Basically if you do

    class A
       attr_reader :b
    end
    
    a = A.new
    

    you can do a.b to access b instance variable. It is important because in every policies you might allow read access of instance variables. @current_user and @user is instance variable.

    1. is it right that i have to initialise the user model the way I have above (or is that only the case in models other than user, since I'm initialising current_user, it might already get the user initialised?

    You have to initialise it manually. Currently, the way you did it is correctly. Good.

    1. is it necessary to move the strong params to the policy, or will this work if I leave them in the controller?

    It is the matter of choice. It will work even if you kept it into controller. Move to policy only if you want to whitelist attributes in quite complex way.

    Note: device , pundit and rolify gem works good but there share some of the same functionality so be careful and consistence what to do with what.

    For example, You can use devise_for :users , :students , :teachers which will give 3 different links to login the respective resources. You can do lot of things with it. You can further authenticate the urls as per the resources with authenticate method. Check https://github.com/plataformatec/devise/wiki/How-To:-Define-resource-actions-that-require-authentication-using-routes.rb This sort of thing can also be done with pundit with policies and rolify with roles.