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

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

Провальное редактирование

В этом разделе мы обработаем случай провального редактирования и получим прохождение теста сообщения об ошибке Листинг 9.1. Код приложения создает действие update которое использует update_attributes (Раздел 6.1.5) для обновления пользователя на основе отправленного хэша params, как это показано в Листинге 9.8. С невалидной информацией, попытка обновления вернет false и ветка else заново отрендерит страницу редактирования. Мы видели этот способ ранее; структура очень похожа на первую версию действия create (Листинге 7.21).

class UsersController < ApplicationController
  .
  .
  .
  def edit
    @user = User.find(params[:id])
  end

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      # Handle a successful update.
    else
      render 'edit'
    end
  end
  .
  .
  .
end
Листинг 9.8. Начальное действие update контроллера Users. app/controllers/users_controller.rb

Обратите внимание на использование user_params в вызове update_attributes, который использует строгие параметры для предотвращения уязвимости массового назначения (как было описано в Разделе 7.3.2).

Получившееся в результате сообщение об ошибке ( рис. 9.3) - именно то что нам нужно для прохождения теста, что вам следует проверить запустив набор тестов:

$ bundle exec rspec spec/
Сообщение об ошибке после отправления формы обновления.

Рис. 9.3. Сообщение об ошибке после отправления формы обновления.

Успешное редактирование

Теперь пришло время заставить работать форму редактирования. Редактирование профильных изображений уже работает поскольку мы переложили загрузку изображений на Gravatar; мы можем отредактировать граватар, кликнув по ссылке "change", показанной на рис. 9.2, как это показано на рис. 9.4. Давайте заставим работать остальной функционал редактирования пользователя.

Интерфейс обрезки изображений Gravatar с фоткой какого-то чувака.

Рис. 9.4. Интерфейс обрезки изображений Gravatar с фоткой какого-то чувака.

Тесты для update действия похожи на аналогичные для create действия. Листинг 9.9 показывает как использовать Capybara для заполнения полей формы валидной информацией, а затем тестирует, что результирующее поведение корректно. Это большой кусок кода; посмотрим, сможете ли вы проработать его, опираясь на тесты из Главы 7.

require 'spec_helper'

describe "User pages" do
  .
  .
  .
  describe "edit" do
    let(:user) { FactoryGirl.create(:user) }
    before do
      sign_in user
      visit edit_user_path(user)
    end
    .
    .
    .
    describe "with valid information" do
      let(:new_name)  { "New Name" }
      let(:new_email) { "new@example.com" }
      before do
        fill_in "Name",             with: new_name
        fill_in "Email",            with: new_email
        fill_in "Password",         with: user.password
        fill_in "Confirm Password", with: user.password
        click_button "Save changes"
      end

      it { should have_title(new_name) }
      it { should have_selector('div.alert.alert-success') }
      it { should have_link('Sign out', href: signout_path) }
      specify { expect(user.reload.name).to  eq new_name }
      specify { expect(user.reload.email).to eq new_email }
    end
  end
end
Листинг 9.9. Тесты для update действия контроллера Users. spec/requests/user_pages_spec.rb

Обратите внимание на то что Листинг 9.9 добавляет метод sign_in из Листинга 9.6 в блок before, что необходимо для прохождения тестов ссылки "Sign out" и также предполагает защиту действия edit от невошедших пользователей (Раздел 9.2.1).

Единственной новинкой в Листинг 9.9 является метод reload, который появляется в тесте для изменения атрибутов пользователя:

specify { expect(user.reload.name).to  eq new_name }
specify { expect(user.reload.email).to eq new_email }

Этот код перезагружает user переменную из (тестовой) базы данных используя user.reload, а затем проверяет, что новые имя пользователя и email совпадают с новыми значениями.

Действие update, необходимое для прохождения тестов в Листинге 9.9 аналогично финальной форме create действия (Листин 8.27), как видно в Листинг 9.10. Единственное что он делает, это добавляет

flash[:success] = "Profile updated"
redirect_to @user

к коду в Листинг 9.8.

class UsersController < ApplicationController
  .
  .
  .
  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
  end
  .
  .
  .
end
Листинг 9.10. Дейcтвие update контроллера Users. app/controllers/users_controller.rb

Обратите внимание, что сейчас каждое редактирование требует от пользователя повторного подтверждения пароля (как следует из пустого текстового поля подтверждения на рис. 9.2), что делает обновление более безопасным, но вызывающим незначительное раздражение.

С кодом из этого раздела, страница редактирования пользователя должна работать, что вы можете проверить, перезапустив набор тестов, который в данный момент должен быть полностью зеленым:

$ bundle exec rspec spec/

Авторизация

Одним из приятных эффектов построения механизма аутентификации в Главе 8 является то, что мы теперь готовы к реализации авторизации: аутентификация позволяет нам идентифицировать пользователей нашего сайта, а авторизация позволяет нам контролировать предоставляемые им возможности.

Хотя edit и update действия из Раздела 9.1 функционально завершены, они страдают от нелепой бреши в безопасности: они позволяют любым (даже незарегистрированным) пользователям иметь доступ к любому действию, и любой зарегистрированный пользователь может изменять информацию любого другого пользователя. В этом разделе мы реализуем модель безопасности, которая будет требовать от пользователей входа в систему и будет предотвращать обновление любой информации, кроме их собственной. Незарегистрированные пользователи, пытающиеся получить доступ к защищенным страницам будут перенаправлены на страницу входа с полезным сообщением, как это показано на рис. 9.5.

Набросок результата посещения защищенной страницы

Рис. 9.5. Набросок результата посещения защищенной страницы

Требование входа пользователей

Поскольку ограничения безопасности для edit и update действий идентичны, мы будем обрабатывать их в одном RSpec describe блоке. Начав с требования входа, наши первоначальные тесты затем проверяют, что не вошедшие пользователи, пытающиеся получить доступ к какому либо из действий, просто перенаправляеются на страницу входа, как показано в Листинге 9.11.

require 'spec_helper'

describe "Authentication" do
  .
  .
  .
  describe "authorization" do

    describe "for non-signed-in users" do
      let(:user) { FactoryGirl.create(:user) }

      describe "in the Users controller" do

        describe "visiting the edit page" do
          before { visit edit_user_path(user) }
          it { should have_title('Sign in') }
        end

        describe "submitting to the update action" do
          before { patch user_path(user) }
          specify { expect(response).to redirect_to(signin_path) }
        end
      end
    end
  end
end
Листинг 9.11. Тестирование того, что edit и update действия защищены. spec/requests/authentication_pages_spec.rb

Код в Листинге 9.11 вводит второй способ, отличающийся от метода visit предоставляемого Capybara, для доступа к действию контроллера: выдавая соответствующий HTTP запрос непосредственно, в данном случае, с помощью метода patch для выдачи запроса PATCH:

describe "submitting to the update action" do
  before { patch user_path(user) }
  specify { expect(response).to redirect_to(signin_path) }
end

Это выдает запрос PATCH непосредственно к /users/1, который направляет к update действию контроллера Users (Таблица 7.1, лекция 7). Это необходимо из-за того, что браузер не может посетить непосредственно само действие update — он может лишь попасть туда через отправку формы редактирования — так что Capybara тоже не может этого сделать. Но посещение страницы редактирования тестирует только авторизацию для действия edit, но не для update. В результате, единственный способ как следует протестировать авторизацию для самого действия update это выдать непосредственный запрос. (Как вы можете догадаться, в дополнение к patch Rails тесты поддерживают также get, post, и delete.)

При использовании одного из способов непосредственной выдачи HTTP запросов, мы получаем доступ к низкоуровневому объекту response. В отличие от объекта Capybara page, response позволяет нам тестировать сам ответ сервера, в данном случае, проверяя что действие update отвечает переадресацией на страницу входа:

specify { expect(response).to redirect_to(signin_path) }

Авторизационный код приложения использует предфильтр, который использует before_action команду для указания конкретному методу быть вызванным до данных действий. (Раньше команда для предфильтров называлась before_filter, но ядро разработчиков Rails решило переименовать его для того чтобы подчеркнуть что фильтр исполняется перед заданным действием контроллера.) Для того чтобы требовать от пользователей входа, мы определяем signed_in_user метод и вызываем его с помощью before_action :signed_in_user, как это показано в Листинге 9.12.

class UsersController < ApplicationController
  before_action :signed_in_user, only: [:edit, :update]
  .
  .
  .
  private

    def user_params
      params.require(:user).permit(:name, :email, :password,
                                   :password_confirmation)
    end

    # Before filters

    def signed_in_user
      redirect_to signin_url, notice: "Please sign in." unless signed_in?
    end
end
Листинг 9.12. Добавление предфильтра signed_in_user. app/controllers/users_controller.rb

По умолчанию, предфильтры применяются ко всем действиям контроллера, поэтому мы ограничиваем действие фильтра только :edit и :update действиями, посредством передачи соответствующего хэша опций :only.

Обратите внимание, что Листинге 9.12 использует сокращение для установки flash[:notice] передавая хэш опций в функцию redirect_to. Код в Листинге 9.12 эквивалентен более многословному

unless signed_in?
  flash[:notice] = "Please sign in."
  redirect_to signin_url
end

(К сожалению данная конструкция не работает для ключей :error и :success.)

Совместно с :success и :error, ключ :notice завершает наш триумвират отстиленных flash, поддерживаемых Bootstrap CSS фреймворком. Выйдя из сайта и попытавшись получить доступ к странице редактирования пользователя /users/1/edit, мы можем увидеть результирующий желтый блок "notice", как это показано на рис. 9.6.

Форма входа после попытки получить доступ к защищенной странице.

Рис. 9.6. Форма входа после попытки получить доступ к защищенной странице.

В этой точке наш набор тестов должен решительно позеленеть:

$ bundle exec rspec spec/
Вадим Обозин
Вадим Обозин

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