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 .