Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора? |
Обновление, демонстрация и удаление пользователей
Destroy действие
Последний шаг, необходимый для завершения ресурса Users, заключается в добавлении удаляющих ссылок и destroy действия. Мы начнем с добавления удаляющей ссылки для каждого пользователя на страницу со списком пользователей, при этом ограничив доступ к ним.
Для написания тестов для удаляющего функционала нам пригодится фабрика создающая администраторов. Мы можем достигнуть этого, добавив блок :admin к нашим фабрикам как это показано в Листинг 9.41.
FactoryGirl.define do factory :user do sequence(:name) { |n| "Person #{n}" } sequence(:email) { |n| "person_#{n}@example.com"} password "foobar" password_confirmation "foobar" factory :admin do admin true end end endЛистинг 9.41. Добавление фабрики для административных пользователей. spec/factories.rb
С кодом в Листинге 9.41, мы теперь можем использовать FactoryGirl.create(:admin) для создания административных пользователей в наших тестах.
Наша политика безопасности требует запретить обычным пользователям видеть удаляющие ссылки:
it { should_not have_link('delete') }
Но административные пользователи должны видеть такие ссылки и мы ожидаем, что админ, кликнув по удаляющей ссылке удалит пользователя, т.e., изменит количество User на -1:
it { should have_link('delete', href: user_path(User.first)) } it "should be able to delete another user" do expect do click_link('delete', match: :first) end.to change(User, :count).by(-1) end it { should_not have_link('delete', href: user_path(admin)) }
Это включает код match: :first, который говорит Capybara что нам не важно какую именно удаляющую ссылку она (Капибара) кликает; это должен быть просто клик по первой из тех что она видит. Обратите внимание что мы также добавили тест для проверки того что административный пользователь не видит ссылки на удаление самого себя. Полный набор тестов для удаляющих ссылок представлен в Листинге 9.42.
require 'spec_helper' describe "User pages" do subject { page } describe "index" do let(:user) { FactoryGirl.create(:user) } before do sign_in user visit users_path end it { should have_title('All users') } it { should have_content('All users') } describe "pagination" do . . . end describe "delete links" do it { should_not have_link('delete') } describe "as an admin user" do let(:admin) { FactoryGirl.create(:admin) } before do sign_in admin visit users_path end it { should have_link('delete', href: user_path(User.first)) } it "should be able to delete another user" do expect do click_link('delete', match: :first) end.to change(User, :count).by(-1) end it { should_not have_link('delete', href: user_path(admin)) } end end end . . . endЛистинг 9.42. Тесты для удаляющих ссылок. spec/requests/user_pages_spec.rb
Код приложения показывает удаляющие ссылки только если текущий пользователь является админом (Листинг 9.43). Обратите внимание на аргумент method: :delete, который организует выдачу ссылками необходимого запроса DELETE. Мы также обернули каждую ссылку в if выражение, таким образом они видны только администраторам. Результат, видимый нашим административным пользователям, представлен на Рис. 9.14.
<li> <%= gravatar_for user, size: 52 %> <%= link_to user.name, user %> <% if current_user.admin? && !current_user?(user) %> | <%= link_to "delete", user, method: :delete, data: { confirm: "You sure?" } %> <% end %> </li>Листинг 9.43. Ссылки удаляющие пользователей (видны только администраторам). app/views/users/_user.html.erb
Изначально, веб браузеры не могут отправлять DELETE запросы, и Rails подделывает их с помощью JavaScript. Это означает, что удаляющие ссылки не будут работать если у пользователя отключен JavaScript. Если вы обязаны поддерживать браузеры с отключенным JavaScript, вы можете подделать запрос DELETE с помощью формы и запроса POST, что будет работать даже без JavaScript; более подробно об этом см. RailsCast о "Destroy Without JavaScript".
Для того чтобы получить рабочие удаляющие ссылки, нам необходимо добавить действие destroy(Таблица 7.1, лекция 7), которое будет находить соответствующего пользователя и удалять его с помощью метода Active Record destroy, по завершении перенаправляя пользователя на страницу списка пользователей, как это показано в Листинге 9.44. Обратите внимание, что мы также добавили :destroy в предфильтр signed_in_user.
class UsersController < ApplicationController before_action :signed_in_user, only: [:index, :edit, :update, :destroy] before_action :correct_user, only: [:edit, :update] . . . def destroy User.find(params[:id]).destroy flash[:success] = "User deleted." redirect_to users_url end . . . endЛистинг 9.44. Добавление рабочего действия destroy. app/controllers/users_controller.rb
Обратите внимание на то, что действие destroy использует сцепление методов для того, чтобы скомбинировать find и destroy в одну строку:
User.find(params[:id]).destroy
Даже несмотря на то, что только администраторы могут видеть ссылки на удаление, есть еще одна страшная дыра в безопасности: любой достаточно опытный злоумышленник может просто выдать запрос DELETE из командной строки и удалить любого пользователя на сайте. Для того, чтобы обеспечить безопасность сайта, мы также нуждаемся в контроле доступа, и наши тесты должны проверить не только то, что администраторы могут удалять пользователей, но также и то, что другие пользователи не могут этого делать. Результаты представлены в Листинге 9.45. Обратите внимание на то, что, по аналогии с методом patch из Листинга 9.11, мы используем delete для непосредственной выдачи DELETE к указанному URL (в данном случае, путь к пользователю, как того требует Таблица 7.1, лекция 7).
require 'spec_helper' describe "Authentication" do . . . describe "authorization" do . . . describe "as non-admin user" do let(:user) { FactoryGirl.create(:user) } let(:non_admin) { FactoryGirl.create(:user) } before { sign_in non_admin, no_capybara: true } describe "submitting a DELETE request to the Users#destroy action" do before { delete user_path(user) } specify { expect(response).to redirect_to(root_url) } end end end endЛистинг 9.45. Тест для защиты действия destroy. spec/requests/authentication_pages_spec.rb
В принципе, у нас осталась еще одна незначительная брешь в безопасности, которая заключается в том, что админ может удалить сам себя выдав запрос DELETE. Можно, конечно, сказать что такой админ Сам Себе Злой Буратино, но было бы неплохо предотвратить подобные случаи, что остается в качестве упражнения (Раздел 9.6).
Как вы можете догадаться, реализация использует предфильтр, в этот раз для ограничения доступа к destroy действию всем пользователям кроме администраторов. Получившийся в результате предфильтр admin_user представлен в Листинге 9.46.
class UsersController < ApplicationController before_action :signed_in_user, only: [:index, :edit, :update, :destroy] before_action :correct_user, only: [:edit, :update] before_action :admin_user, only: :destroy . . . private . . . def admin_user redirect_to(root_url) unless current_user.admin? end endЛистинг 9.46. Предфильтр открывающий доступ к действию destroy только админам. app/controllers/users_controller.rb
В этой точке все тесты должны проходить и ресурс Users — со своим контроллером, моделью и представлениями — функционально завершен.
$ bundle exec rspec spec/