Pundit — это инструмент, который позволяет вам ограничивать доступ к определенным частям вашего Rails-приложения только авторизованным пользователям. Это достигается путем предоставления вам определенных помощников.
В этом руководстве вы создадите блог, который ограничивает такие части, как создание, обновление и удаление статей только для авторизованных пользователей.
Начиная
Начните с создания нового приложения Rails.
1
|
rails new pundit-blog -T
|
Флаг -T
указывает Rails генерировать новое приложение без набора тестов по умолчанию. Выполнение команды сгенерирует ваше Rails-приложение и установит гемы по умолчанию.
Идите вперед и добавьте следующие драгоценные камни в свой Gemfile. Вы будете использовать bootstrap-sass для макета вашего приложения, а Devise будет обрабатывать аутентификацию пользователя.
1
2
3
4
5
|
#Gemfile
…
gem ‘bootstrap-sass’
gem ‘devise’
|
Запустите команду, чтобы установить гем.
1
|
bundle install
|
Теперь переименуйте app/assets/stylesheets/application.css
в app/assets/stylesheets/application.scss
. Добавьте следующие строки кода для импорта начальной загрузки.
1
2
3
4
5
|
#app/assets/stylesheets/application.scss
…
@import ‘bootstrap-sprockets’;
@import ‘bootstrap’;
|
Создайте _navigation.html.erb
с именем _navigation.html.erb
для хранения вашего кода навигации; частичное должно быть расположено в каталоге app / views / layouts . Сделайте частичное похожее на то, что у меня ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#app/views/layouts/_navigation.html.erb
<nav class=»navbar navbar-inverse»>
<div class=»container»>
<div class=»navbar-header»>
<%= link_to ‘Pundit Blog’, root_path, class: ‘navbar-brand’ %>
</div>
<div id=»navbar»>
<ul class=»nav navbar-nav pull-right»>
<li><% link_to ‘Home’, root_path %></li>
<ul class=»nav navbar-nav pull-right»>
<% if user_signed_in?
<li><%= current_user.email %></li>
<li><%= link_to ‘Log out’, destroy_user_session_path, method: :delete %></li>
<% else %>
<li><%= link_to ‘Log In’, new_user_session_path %></li>
<li><%= link_to ‘Sign Up’, new_user_registration_path %></li>
<% end %>
</ul>
</ul>
</div>
</nav>
|
Для использования навигации вам необходимо отобразить ее в макете приложения. Настройте макет приложения, чтобы он выглядел так, как показано ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Pundit-Blog</title>
<%= csrf_meta_tags %>
<%= stylesheet_link_tag ‘application’, media: ‘all’, ‘data-turbolinks-track’: ‘reload’ %>
<%= javascript_include_tag ‘application’, ‘data-turbolinks-track’: ‘reload’ %>
</head>
<body>
<%= render «layouts/navigation» %>
<div id=»flash»>
<% flash.each do |key, value|
<div class=»flash <%= key %>»><%= value %></div>
<% end %>
</div>
<div class=»container-fluid»>
<%= yield %>
</div>
</body>
</html>
|
Генерация пользовательской модели
Запустите команду для установки Devise.
1
|
rails generate devise:install
|
Теперь создайте свою модель пользователя.
1
|
rails generate devise User
|
Перенос вашей базы данных.
1
|
rake db:migrate
|
Создать статью ресурсов
Запустите команду для генерации ваших ресурсов статьи.
1
|
rails generate scaffold Articles title:string body:text
|
Это сгенерирует ваш ArticlesController
и Article Model. Это также сгенерирует необходимые представления.
Теперь перенесите вашу базу данных, запустив:
1
|
rake db:migrate
|
Откройте app/views/articles/_form.html.erb
и сделайте так, как app/views/articles/_form.html.erb
ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#app/views/articles/_form.html.erb
<%= form_for(article) do |f|
<% if article.errors.any?
<div id=»error_explanation»>
<h2><%= pluralize(article.errors.count, «error») %> prohibited this article from being saved:</h2>
<ul>
<% article.errors.full_messages.each do |message|
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class=»field»>
<%= f.label :title %>
<%= f.text_field :title %>
</div>
<div class=»field»>
<%= f.label :body %>
<%= f.text_area :body %>
</div>
<div class=»actions»>
<%= f.submit %>
</div>
<% end %>
|
Для вашего индексного файла это должно выглядеть так.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#app/views/articles/index.html.erb
<table class=»table table-bordered table-striped table-condensed table-hover»>
<thead>
<tr>
<th>Title</th>
<th>Body</th>
<th colspan=»3″></th>
</tr>
</thead>
<tbody>
<% @articles.each do |article|
<tr>
<td><%= article.title %></td>
<td><%= article.body %></td>
<td><%= link_to ‘Show’, article %></td>
<td><%= link_to ‘Edit’, edit_article_path(article) %></td>
<td><%= link_to ‘Destroy’, article, method: :delete, data: { confirm: ‘Are you sure?’
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to ‘New article’, new_article_path %>
|
Приведенный выше код упорядочивает статьи на индексной странице в виде таблицы, чтобы она выглядела презентабельно.
Откройте файл маршрутов и добавьте маршрут к статьям ресурсов.
1
2
3
4
5
|
#config/routes.rb
…
resources :articles
root to: «articles#index»
|
Интегрировать Pundit
Добавьте драгоценный камень Pundit в свой Gemfile.
1
2
3
4
|
#Gemfile
…
gem ‘pundit’
|
Запустите команду для установки.
1
|
bundle install
|
Интегрируйте Pundit в свое приложение, добавив следующую строку в ApplicationController .
1
2
3
4
5
|
#app/controllers/application_controller.rb
…
include Pundit
…
|
Запустите генератор Pundit.
1
|
rails g pundit:install
|
Это создаст папку приложения / политики, которая содержит базовый класс с политиками. Каждая политика является базовым классом Ruby.
Так выглядит политика базового класса.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
#app/policies/application_policy.rb
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
false
end
def show?
scope.where(:id => record.id).exists?
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
scope
end
end
end
|
Создать политику статьи
Теперь вам нужно написать собственную политику. В этом руководстве вы хотите, чтобы только зарегистрированные пользователи могли создавать новые статьи. Кроме того, редактировать и удалять статью могут только создатели статьи.
Чтобы добиться этого, ваша политика статей будет выглядеть следующим образом.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#app/policies/article_policy.rb
class ArticlePolicy < ApplicationPolicy
def index?
true
end
def create?
user.present?
end
def update?
return true if user.present?
end
def destroy?
return true if user.present?
end
private
def article
record
end
end
|
Выше вы разрешаете всем (зарегистрированным и незарегистрированным пользователям) видеть страницу указателя. Чтобы создать новую статью, пользователь должен быть зарегистрирован. Вы используете user.present?
узнать, зарегистрирован ли пользователь, пытающийся выполнить действие.
Для обновления и удаления вы должны убедиться, что только пользователь, создавший статью, может выполнять эти действия.
На этом этапе вам необходимо установить связь между вашей статьей и моделью пользователя.
Вы делаете это, генерируя новую миграцию.
1
|
rails generate migration add_user_id_to_articles user:references
|
Затем перенесите базу данных, выполнив команду:
1
|
rake db:migrate
|
Откройте модель User и добавьте линию, которая запечатывает отношения.
1
2
3
4
|
#app/models/user.rb
…
has_many :articles
|
Ваша модель статьи должна иметь это.
1
2
3
4
|
#app/models/article.rb
…
belongs_to :user
|
Теперь вам нужно обновить ваш ArticlesController
чтобы он синхронизировался с тем, что вы уже сделали.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
#app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
# GET /articles
# GET /articles.json
def index
@articles = Article.all
authorize @articles
end
# GET /articles/1
# GET /articles/1.json
def show
end
# GET /articles/new
def new
@article = Article.new
authorize @article
end
# GET /articles/1/edit
def edit
end
# POST /articles
# POST /articles.json
def create
@article = Article.new(article_params)
@article.user = current_user
authorize @article
respond_to do |format|
if @article.save
format.html { redirect_to @article, notice: ‘Article was successfully created.’
format.json { render :show, status: :created, location: @article }
else
format.html { render :new }
format.json { render json: @article.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /articles/1
# PATCH/PUT /articles/1.json
def update
respond_to do |format|
if @article.update(article_params)
format.html { redirect_to @article, notice: ‘Article was successfully updated.’
format.json { render :show, status: :ok, location: @article }
else
format.html { render :edit }
format.json { render json: @article.errors, status: :unprocessable_entity }
end
end
end
# DELETE /articles/1
# DELETE /articles/1.json
def destroy
@article.destroy
respond_to do |format|
format.html { redirect_to articles_url, notice: ‘Article was successfully destroyed.’
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_article
@article = Article.find(params[:id])
authorize @article
end
# Never trust parameters from the scary internet, only allow the white list through.
def article_params
params.require(:article).permit(:title, :body, :user_id)
end
end
|
На этом этапе в вашем приложении вы успешно внедрили политики, ограничивающие определенные части вашего приложения выбранными пользователями.
Вы хотите добавить стандартное сообщение об ошибке, которое отображается всякий раз, когда неавторизованный пользователь пытается получить доступ к ограниченной странице. Для этого добавьте следующее в ApplicationController
.
01
02
03
04
05
06
07
08
09
10
11
|
#app/controllers/application_controller.rb
…
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
flash[:warning] = «You are not authorized to perform this action.»
redirect_to(request.referrer || root_path)
end
|
Этот код просто отображает основной текст, который сообщает пользователю, что у него нет прав на выполнение действия.
Бегать:
1
|
$ rails server
|
Чтобы запустить свой сервер Rails, укажите в браузере https://localhost:3000
чтобы увидеть, что у вас есть.
Вывод
В этом уроке вы узнали, как работать с Devise и Pundit. Вы смогли создать политики, которые позволяли только авторизованным пользователям просматривать определенные части приложения. Вы также создали основной текст ошибки, который показывает, когда неавторизованный пользователь пытается получить доступ к ограниченной части приложения.
Вы можете узнать больше о Pundit, посетив страницу GitHub .