Authorization with Pundit

I want share my experience in the group from our Monday meetup yesterday. Let’s refresh the memory:

“do authorization with pundit gem” – Homework here on github issue #12

https://rubygems.org/gems/pundit

We went to this page and bundle it to our Gemfile

gem 'pundit', '~> 1.0.1'

And open up these links on that page.

Homepage: https://github.com/elabs/pundit

Documentation: http://www.rubydoc.info/gems/pundit/1.0.1

Previously we have done the authorization in this way writing on the products_controller and views/products/

# app/controllers/products_controller.rb
if @product.user == current_user

and then also implemented after that the same feature in this way

# app/models/product.rb
  def user_owns?(u)
    self.user == u
  end

  def user_can_change?(u)
    self.user_owns?(u)
  end

# app/controllers/products_controller.rb
if @product.user_owns?(current_user)

What about pundit now? We were going to implement the feature with pundit. By documentation, we did following process. (After adding gem 'pundit' to Gemfile and running bundle install on console.)

class ApplicationController < ActionController::Base
  include Pundit
  ...
$ rails g pundit:install

That generates a file application_policy.rb and the documentation example code looks like this.

# app/policies/application_policy.rb
class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    @user = user
    @record = record
  end
  ...

  def update?
    false
  end
  ...

# app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
  def update?
    user.admin? or not record.published?
  end
end

And of course this code was not working on our code, to solve the issue we tried following code and it worked (override update? method)

# app/policies/application_policy.rb
class ApplicationPolicy
  attr_reader :user

  def initialize(user)
    @user = user
  end
  ...

  def update?
    false
  end
  ...

# app/policies/post_policy.rb
class ProductPolicy < ApplicationPolicy
  attr_reader :user, :product

  def initialize(user, product)
    super(user)
    @product = product
  end

  def update?
    user == product.user
  end
end

(By the way, following code works also when I tried just now)

# app/policies/application_policy.rb
class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, :record)
    @user = user
    @record = record
  end
  ...

  def update?
    false
  end
  ...

# app/policies/post_policy.rb
class ProductPolicy < ApplicationPolicy
  # attr_reader :user, :product

  # def initialize(user, product)
  #   super(user)
  #   @product = product
  # end

  def update?
    user == record.user
  end
end

However we changed the controller & views based using methods pundit gives us.

# app/controllers/products_controller.rb
  before_action :set_product, only: [:show, :edit, :update, :destroy]
  ...

  def update
    # here runs set_product as before_action
    authorize @product
    ...

  def edit
    authorize @product
  end
  ...

  private
    def set_product
      @product = Product.find(params[:id])
    end

And then by the way (this was my case) it didn’t work! couldn’t find why! After my panicking, Sean found I had those lines from the last week:

# app/controllers/products_controller.rb
  before_action :authorize_user, only: [:edit, :update, :destroy]
  ...
    def authorize_user
      redirect_to products_url, alert: "not your product!" unless @product.user_can_change?(current_user)

After this, when we commented them out, and applied it in views and controllers, it worked as expected.

# app/controllers/products_controller.rb
  ...
  def edit
    authorize @product
  end

  def update
    authorize @product
  end
  ...
<!-- app/views/products/index.html.erb -->
<table class="table">
    <% @products.each do |product| %>
      <tbody>
        <% if policy(product).update? %>
          <td><%= link_to 'Edit', edit_product_path(product), class: "btn btn-sm btn-default" %></td>
          <td><%= link_to 'Destroy', product, method: :delete, data: { confirm: 'Are you sure?' }, class: "btn btn-sm btn-danger" %></td>
        <% end %>

In addition, the authorize method simply gives Pundit::NotAuthorizedError error, when it’s not authorized (not the flash message we implemented the last time)

# app/controllers/products_controller.rb
  before_action :authorize_user, only: [:edit, :update, :destroy]
  ...
    def authorize_user
      redirect_to products_url, alert: "not your product!" unless policy(@product).update?
    end

However, Sako showed us his code and it’s different from mine (ours) you can check his repo

https://github.com/LARailsLearners/sakoh_app

Let’s review other members’ code. Simply open their repo on our organization page. And let’s share resource links helped you solve problems.

https://github.com/RailsApps/rails-devise-pundit

We found this github repo link (from pundit documentation) as example app with devise and pundit.

you can check repo of my new_app, what we worked yesterday :) by the way.

https://github.com/LARailsLearners/kangkyu_new_app

Thank you for coming, Sako, Jen, Sean, James, Eddie, Kristiana, Kang-Kyu, and anybody missing.

 
3
Kudos
 
3
Kudos

Now read this

minitest setup - Ruby on Rails

I now do testing in Minitest. I came to think it’s better choice to start with minitest for testing Rails. And I thought it would make more sense when I stay out of Minitest::Spec. my-experience-with-minitest-and-rspec by tenderlove... Continue →