Опубликован: 27.01.2016 | Доступ: свободный | Студентов: 916 / 58 | Длительность: 23:07:00
Лекция 9:

Обновление, демонстрация и удаление пользователей

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".

Страница списка пользователей /users с удаляющими ссылками.

Рис. 9.14. Страница списка пользователей /users с удаляющими ссылками.

Для того чтобы получить рабочие удаляющие ссылки, нам необходимо добавить действие 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/
Вадим Обозин
Вадим Обозин

Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора?