Advertisement
  1. Code
  2. Ruby on Rails

Autorización Con Pundit

Scroll to top
Read Time: 8 min

() translation by (you can also view the original English article)

Pundit es una herramienta que te permite restringir ciertas partes de tu aplicación Rails a usuarios autorizados. Hace esto proporcionándote ciertos ayudantes.

En este tutorial, construirás un blog que restringe partes tales como crear, actualizar y borrar artículos solo para usuarios autorizados.

Comenzando

Comienza generando una nueva aplicación Rails.

1
rails new pundit-blog -T

La bandera -T le dice a Rails que genere la nueva aplicación sin la suite de prueba por defecto. Ejecutar el comando generará tu aplicación Rails e instalará los gems por defecto.

Continúa y agrega los siguientes gems a tu Gemfile. Estarás usando bootstrap-sass para el diseño de tu aplicación, y Devise manejará la autenticación de usuario.

1
#Gemfile
2
3
...
4
gem 'bootstrap-sass'
5
gem 'devise'

Ejecuta el comando para instalar el gem.

1
bundle install

Ahora renombra app/assets/stylesheets/application.cssapp/assets/stylesheets/application.scss. Agrega las siguientes líneas de código para importar bootstrap.

1
#app/assets/stylesheets/application.scss
2
3
...
4
@import 'bootstrap-sprockets';
5
@import 'bootstrap';

Crea un parcial llamado _navigation.html.erb para contener tu código de navegación; el parcial debería estar ubicado en el directorio app/views/layouts. Haz que el parcial luzca como lo que tengo abajo.

1
#app/views/layouts/_navigation.html.erb
2
3
<nav class="navbar navbar-inverse">
4
  <div class="container">
5
    <div class="navbar-header">
6
      <%= link_to 'Pundit Blog', root_path, class: 'navbar-brand' %>
7
    </div>
8
    <div id="navbar">
9
 
10
    <ul class="nav navbar-nav pull-right">
11
      <li><% link_to 'Home', root_path %></li>
12
      <ul class="nav navbar-nav pull-right">
13
        <% if user_signed_in? %>
14
        <li><%= current_user.email %></li>
15
        <li><%= link_to 'Log out', destroy_user_session_path, method: :delete %></li>
16
        <% else %>
17
          <li><%= link_to 'Log In', new_user_session_path %></li>
18
          <li><%= link_to 'Sign Up', new_user_registration_path %></li>
19
        <% end %>
20
      </ul>
21
    </ul>
22
  </div>
23
</nav>

Para que la navegación sea usada, necesitas generarla en tu diseño de aplicación. Modifica tu diseño de aplicación para que luzca como lo que tengo abajo.

1
#app/views/layouts/application.html.erb
2
3
<!DOCTYPE html>
4
<html>
5
  <head>
6
    <title>Pundit-Blog</title>
7
    <%= csrf_meta_tags %>
8
9
    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
10
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
11
  </head>
12
13
  <body>
14
    <%= render "layouts/navigation" %>
15
    <div id="flash">
16
      <% flash.each do |key, value| %>
17
        <div class="flash <%= key %>"><%= value %></div>
18
      <% end %>
19
    </div>
20
    <div class="container-fluid">
21
      <%= yield %>
22
    </div>
23
  </body>
24
</html>

General el Modelo de Usuario

Ejecuta el comando para instalar Devise.

1
rails generate devise:install

Ahora genera tu modelo de Usuario.

1
rails generate devise User

Migra tu base de datos.

1
rake db:migrate

Genera Recursos de Artículo

Ejecuta el comando para generar tus recursos de Artículo.

1
rails generate scaffold Articles title:string body:text

Esto generará tu ArticlesController y Modelo de Artículo. También generará las vistas necesarias.

Ahora migra tu base de datos ejecutando:

1
rake db:migrate

Abre app/views/articles/_form.html.erb y hazlo lucir como lo que tengo abajo.

1
#app/views/articles/_form.html.erb
2
3
<%= form_for(article) do |f| %>
4
  <% if article.errors.any? %>
5
    <div id="error_explanation">
6
      <h2><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h2>
7
8
      <ul>
9
      <% article.errors.full_messages.each do |message| %>
10
        <li><%= message %></li>
11
      <% end %>
12
      </ul>
13
    </div>
14
  <% end %>
15
16
  <div class="field">
17
    <%= f.label :title %>
18
    <%= f.text_field :title %>
19
  </div>
20
21
  <div class="field">
22
    <%= f.label :body %>
23
    <%= f.text_area :body %>
24
  </div>
25
26
  <div class="actions">
27
    <%= f.submit %>
28
  </div>
29
<% end %>

Para tu archivo index, debería lucir así.

1
#app/views/articles/index.html.erb
2
3
<table class="table table-bordered table-striped table-condensed table-hover">
4
  <thead>
5
  <tr>
6
    <th>Title</th>
7
    <th>Body</th>
8
    <th colspan="3"></th>
9
  </tr>
10
  </thead>
11
12
  <tbody>
13
    <% @articles.each do |article| %>
14
    <tr>
15
      <td><%= article.title %></td>
16
      <td><%= article.body %></td>
17
      <td><%= link_to 'Show', article %></td>
18
      <td><%= link_to 'Edit', edit_article_path(article) %></td>
19
      <td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %></td>
20
    </tr>
21
    <% end %>
22
  </tbody>
23
</table>
24
25
<br>
26
27
<%= link_to 'New article', new_article_path %>

El código de arriba acomoda los artículos en la página index en un formato de tabla para hacerlo lucir presentable.

Abre tu archivo de rutas y agrega la rut apara los recursos de artículos.

1
#config/routes.rb
2
3
...
4
  resources :articles
5
  root to: "articles#index"

Integra Pundit

Agrega el gem Pundit a tu Gemfile.

1
#Gemfile
2
3
...
4
gem 'pundit'

Ejecuta el comando para instalar.

1
bundle install

Integra Pundit a tu aplicación agregando la siguiente línea a tu ApplicationController.

1
#app/controllers/application_controller.rb
2
3
...
4
  include Pundit
5
...

Ejecuta el generador de Pundit.

1
rails g pundit:install

Esto generará una carpeta app/policies que contiene una clase base con políticas. Cada política es una clase Ruby básica.

Así es como luce la clase básica de política.

1
#app/policies/application_policy.rb
2
3
class ApplicationPolicy
4
  attr_reader :user, :record
5
6
  def initialize(user, record)
7
    @user = user
8
    @record = record
9
  end
10
11
  def index?
12
    false
13
  end
14
15
  def show?
16
    scope.where(:id => record.id).exists?
17
  end
18
19
  def create?
20
    false
21
  end
22
23
  def new?
24
    create?
25
  end
26
27
  def update?
28
    false
29
  end
30
31
  def edit?
32
    update?
33
  end
34
35
  def destroy?
36
    false
37
  end
38
39
  def scope
40
    Pundit.policy_scope!(user, record.class)
41
  end
42
43
  class Scope
44
    attr_reader :user, :scope
45
46
    def initialize(user, scope)
47
      @user = user
48
      @scope = scope
49
    end
50
51
    def resolve
52
      scope
53
    end
54
  end
55
end

Crea la Política de Artículo

Ahora necesitas escribir tu propia política. Para este tutorial, quieres permitir solo usuarios registrados para que creen nuevos artículos. Además de eso, solo los creadores de un artículo deberían poder editar y borrar el artículo.

Para lograr esto, tu política de artículo lucirá así.

1
#app/policies/article_policy.rb
2
3
class ArticlePolicy < ApplicationPolicy
4
  def index?
5
    true
6
  end
7
8
  def create?
9
    user.present?
10
  end
11
12
  def update?
13
    return true if user.present? && user == article.user
14
  end
15
16
  def destroy?
17
    return true if user.present? && user == article.user
18
  end
19
20
  private
21
22
    def article
23
      record
24
    end
25
end

En lo de arriba, estás permitiendo a todos (usuarios registrados y no registrados) ver la página index. Para crear un nuevo artículo, un usuario tiene que estar registrado. Usas user.present? para averiguar si el usuario que está intentando realizar la acción está registrado.

Para actualizar y borrar, quieres asegurarte de que solo el usuario que creó el artículo pueda realizar estas acciones.

En este punto, necesitas establecer una relación entre tu modelo de Artículo y Usuario.

Lo haces generando una nueva migración.

1
rails generate migration add_user_id_to_articles user:references

Después, migra tu base de datos ejecutando el comando:

1
rake db:migrate

Abre el modelo User y agrega la línea que sella la relación.

1
#app/models/user.rb
2
3
...
4
  has_many :articles

Tu modelo Article debería tener esto.

1
#app/models/article.rb
2
3
...
4
  belongs_to :user

Ahora necesitas actualizar tu ArticlesController para que esté en sincronía con lo que has hecho hasta ahora.

1
#app/controllers/articles_controller.rb
2
3
class ArticlesController < ApplicationController
4
  before_action :set_article, only: [:show, :edit, :update, :destroy]
5
6
  # GET /articles
7
  # GET /articles.json
8
  def index
9
    @articles = Article.all
10
    authorize @articles
11
  end
12
13
  # GET /articles/1
14
  # GET /articles/1.json
15
  def show
16
  end
17
18
  # GET /articles/new
19
  def new
20
    @article = Article.new
21
    authorize @article
22
  end
23
24
  # GET /articles/1/edit
25
  def edit
26
  end
27
28
  # POST /articles
29
  # POST /articles.json
30
  def create
31
    @article = Article.new(article_params)
32
    @article.user = current_user
33
    authorize @article
34
35
    respond_to do |format|
36
      if @article.save
37
        format.html { redirect_to @article, notice: 'Article was successfully created.' }
38
        format.json { render :show, status: :created, location: @article }
39
      else
40
        format.html { render :new }
41
        format.json { render json: @article.errors, status: :unprocessable_entity }
42
      end
43
    end
44
  end
45
46
  # PATCH/PUT /articles/1
47
  # PATCH/PUT /articles/1.json
48
  def update
49
    respond_to do |format|
50
      if @article.update(article_params)
51
        format.html { redirect_to @article, notice: 'Article was successfully updated.' }
52
        format.json { render :show, status: :ok, location: @article }
53
      else
54
        format.html { render :edit }
55
        format.json { render json: @article.errors, status: :unprocessable_entity }
56
      end
57
    end
58
  end
59
60
  # DELETE /articles/1
61
  # DELETE /articles/1.json
62
  def destroy
63
    @article.destroy
64
    respond_to do |format|
65
      format.html { redirect_to articles_url, notice: 'Article was successfully destroyed.' }
66
      format.json { head :no_content }
67
    end
68
  end
69
70
  private
71
    # Use callbacks to share common setup or constraints between actions.
72
    def set_article
73
      @article = Article.find(params[:id])
74
      authorize @article
75
    end
76
77
    # Never trust parameters from the scary internet, only allow the white list through.
78
    def article_params
79
      params.require(:article).permit(:title, :body, :user_id)
80
    end
81
end

En este punto en tu aplicación, has implementado satisfactoriamente políticas que restringen ciertas partes de tu aplicación a usuarios selectos.

Quieres agregar un mensaje de error estándar que muestre cuando un usuario no autorizado intenta acceder a una página restringida. Para hacerlo, agrega lo siguiente a tu ApplicationController.

1
#app/controllers/application_controller.rb
2
3
...
4
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
5
6
  private
7
8
    def user_not_authorized
9
      flash[:warning] = "You are not authorized to perform this action."
10
      redirect_to(request.referrer || root_path)
11
    end

Este código simplemente genera un texto básico que le dice al usuario que no está autorizado para realizar la acción.

Ejecuta:

1
$ rails server

Para comenzar tu servidor Rails, apunta a tu navegador a https://localhost:3000 para ver lo que tienes.

Conclusión

En este tutorial, aprendiste cómo trabajar con Devise y Pundit. Pudiste crear políticas que permitieron solo a usuarios autorizados ver ciertas partes de la aplicación. También creaste un texto de error básico que muestra cuando un usuario no autorizado intenta acceder a una parte restringida de la aplicación.

Puedes aprender más sobre Pundit revisando la página de GitHub.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.