Опубликован: 27.01.2016 | Уровень: для всех | Доступ: платный
Лекция 9:

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

В этой главе мы завершим REST действия для ресурса Users (Таблица 7.1, лекция 7) добавив edit, update, index, и destroy действия. Мы начнем с того, что дадим пользователям возможность обновлять свои профили, что также обеспечит естественную возможность для обеспечения модели безопасности (стало возможным, благодаря аутентификационному коду из Главы 8). Затем мы сделаем список всех пользователей (также требует авторизации), что будет поводом для внедрения образцов данных и постраничного вывода (пагинации). Наконец, мы также добавим возможность удалять пользователей, стиранием их в базе данных. Так как мы не можем позволить любому пользователю обладать такими опасными возможностями, мы позаботимся о создании привилегированного класса административных пользователей (администраторов), авторизованных для удаления других пользователей.

Мы начнем с создания отдельной ветки updating-users:

$ git checkout -b updating-users

Обновление пользователей

Основная идея редактирования информации о пользователе тесно параллельна созданию новых пользователей (Глава 7). Вместо new действия визуализирующего представление для нового пользователя, мы имеем edit действие, отображающее представление для редактирования пользователей; вместо create отвечающего на запрос POST, мы имеем update действие, отвечающее на запрос PATCH (Блок 3.3). Основное отличие заключается в том, что зарегистрироваться может любой человек, но только текущий пользователь должен иметь возможность обновлять свою информацию. Это означает, что нам необходимо обеспечить контроль доступа таким образом, чтобы только авторизированные пользователи могли редактировать и обновлять информацию; аутентификационный механизм из Главы 8 позволит нам использовать предфильтр для обеспечения этого вида контроля

Форма для редактирования

Мы начнем с формы редактирования, набросок которой представлен на рис. 9.1.1 Изображение взято на http://www.flickr.com/photos/sashawolff/4598355045/. Как обычно, мы начнем с тестов. Во-первых, обратите внимание на ссылку для смены изображения Gravatar; если вы зайдете на сайт Gravatar, вы увидите, что страница для добавления или редактирования изображений находится по адресу http://gravatar.com/emails, и мы протестируем наличие на странице edit этого URL. 2 Сайт Gravatar на самом деле переадресует на http://en.gravatar.com/emails, предназначенную для англоговорящих пользователей, но я опустил en часть для возможного использования других языков.

Набросок страницы для редактирования пользователя.

Рис. 9.1. Набросок страницы для редактирования пользователя.

Тесты для формы редактирования пользователя аналогичны тестам для формы создания нового пользователя в Листинге 7.31 из упражнений Главы 7, которые добавляют тест для сообщения об ошибке при отправке неверных данных. Результат представлен в Листинге 9.1.

require 'spec_helper'

describe "User pages" do
  .
  .
  .
  describe "edit" do
    let(:user) { FactoryGirl.create(:user) }
    before { visit edit_user_path(user) }

    describe "page" do
      it { should have_content("Update your profile") }
      it { should have_title("Edit user") }
      it { should have_link('change', href: 'http://gravatar.com/emails') }
    end

    describe "with invalid information" do
      before { click_button "Save changes" }

      it { should have_content('error') }
    end
  end
end
Листинг 9.1. Тесты для страницы редактирования пользователя. spec/requests/user_pages_spec.rb

Для того чтобы написать код приложения, нам необходимо заполнить действие edit контоллера Users. Таблица 7.1, лекции 7 указывает на то, что правильным URL для страницы редактирования пользователя является /users/1/edit (предполагается что id пользователя равен 1). Вспомните что id пользователя доступен в переменной params[:id], а это означает что мы можем найти пользователя с помощью кода в Листинге 9.2.

class UsersController < ApplicationController
  .
  .
  .
  def edit
    @user = User.find(params[:id])
  end
  .
  .
  .
end
Листинг 9.2. Действие edit контроллера Users. app/controllers/users_controller.rb

Для прохождения этих тестов требуется создание соответствующего (edit) представления, показанного в Листинге 9.3. Обратите внимание, как сильно оно походит на представление new user из Листинга 7.17 лекции 7; большое перекрытие предполагает факторинг повторяющгося кода в партиал, который мы оставим в качестве упражнения (Раздел 9.6).

<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="span6 offset3">
    <%= form_for(@user) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.text_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.label :password_confirmation, "Confirm Password" %>
      <%= f.password_field :password_confirmation %>

      <%= f.submit "Save changes", class: "btn btn-large btn-primary" %>
    <% end %>

    <%= gravatar_for @user %>
    <a href="http://gravatar.com/emails">change</a>
  </div>
</div>
Листинг 9.3. Представление user edit. app/views/users/edit.html.erb

Здесь мы повторно использовали общедоступный партиал error_messages, введеный в Разделе 7.3.3.

С переменной экземпляра @user из Листинге 9.2, тесты для страницы редактирования из Листинге 9.1 должны пройти:

$ bundle exec rspec spec/requests/user_pages_spec.rb -e "edit page"

Соответствующая страниц показана на рис. 9.2, который показывает, как Rails автоматически предзаполняет Name и Email поля используя аттрибуты переменной @user.

Начальная страница редактирования пользователя с предзаполненными полями Name & Email.

Рис. 9.2. Начальная страница редактирования пользователя с предзаполненными полями Name & Email.

Посмотрев на исходный HTML для рис. 9.2, как и ожидалось, мы видим тег формы (Листинге 9.4).

<form action="/users/1" class="edit_user" id="edit_user_1" method="post">
  <input name="_method" type="hidden" value="patch" />
  .
  .
  .
</form>
Листинг 9.4. HTML для формы редактирования определенной в Листинге 9.3 и показанной на Рис. 9.2.

Обратите внимание на скрытое поле ввода

<input name="_method" type="hidden" value="patch" />

Поскольку веб-браузеры сами по себе не могут отправлять PATCH запросы (как это требует от них REST конвенция из Таблицы 7.1, лекции 7, Rails подделывает иx с помощью POST запроса и скрытого поля input.3Не беспокойтесь о том, как это работает, детали представляют интерес для разработчиков самого фреймворка Rails, но, по замыслу, не имеют значения для разработчиков Rails приложений.

Стоит также упомянуть здесь еще одну тонкость: код form_for(@user) в Листинге 9.3 абсолютно совпадает с кодом в Листинге 7.17 — так как же Rails узнает, что нужно использовать POST запрос для новых пользователей и PATCH для редактирования уже существующих? Ответ кроется в возможности определения того, что мы имеем дело с новым или уже существующим в базе данных пользователем, посредством булевого метода new_record? библиотеки Active Record:

$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false

При построении формы с помощью form_for(@user), Rails использует POST если @user.new_record? это true и PATCHесли это является false .

В качестве финального штриха мы добавим URL к ссылке на настройки пользователя в навигации сайта. Поскольку она зависит от статуса вошедшего, тест для ссылки "Settings" относится к остальным тестам аутентификации, как это показано в Листинге 9.5. (Было бы неплохо иметь дополнительные тесты которые проверяли бы что подобные ссылки не видны невошедшим пользователям; написание этих тестов остается в качестве упражнения (Раздел 9.6).)

require 'spec_helper'

describe "Authentication" do
    .
    .
    .
    describe "with valid information" do
      let(:user) { FactoryGirl.create(:user) }
      before { sign_in user }

      it { should have_title(user.name) }
      it { should have_link('Profile',     href: user_path(user)) }
      it { should have_link('Settings',    href: edit_user_path(user)) }
      it { should have_link('Sign out',    href: signout_path) }
      it { should_not have_link('Sign in', href: signin_path) }
      .
      .
      .
    end
  end
end
Листинг 9.5. Добавление теста для ссылки “Settings”. spec/requests/authentication_pages_spec.rb

Для удобства, код в Листинге 9.5 использует хелпер для входа пользователя внутри тестов. Методика заключается в посещении страницы и отправке валидной информации, как это показано в Листинге 9.6.

.
.
.
def sign_in(user, options={})
  if options[:no_capybara]
    # Sign in when not using Capybara.
    remember_token = User.new_remember_token
    cookies[:remember_token] = remember_token
    user.update_attribute(:remember_token, User.encrypt(remember_token))
  else
    visit signin_path
    fill_in "Email",    with: user.email
    fill_in "Password", with: user.password
    click_button "Sign in"
  end
end
Листинг 9.6. Тестовый хелпер для входа пользователей. spec/support/utilities.rb

Как было отмечено в комментарии, заполнение формы не будет работать в отсутствие Capybara и для того чтобы покрыть этот случай мы позволяем пользователю передать опцию no_capyabara: true для переписывания дефолтного метода входа и непосредственной манипуляции куками. Это нобходимо при использовании одного из методов HTTP запроса напрямую (get, post, patch или delete), как мы увидим в Листинге 9.45. (Обратите внимание: тестовый объект cookies - не самая замечательная симуляция реальных куки; в частности, метод cookies.permanent который мы видели в Листинге 8.19 не работает внутри тестов.) Как вы могли ожидать, метод sign_in пригодится в будущих тестах и фактически он уже может быть использован для устранения нескольких повторений (Раздел 9.6).

Код приложения необходимый для добавления URL к ссылке "Settings" прост: мы применим именованный маршрут edit_user_path из Таблицы 7.1, лекции 7, совместно с удобным вспомогательным методом current_user определенным в Листинге 8.22:

<%= link_to "Settings", edit_user_path(current_user) %>

Полный код приложения представлен в Листинге 9.7).

<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="navbar-inner">
    <div class="container">
      <%= link_to "sample app", root_path, id: "logo" %>
      <nav>
        <ul class="nav pull-right">
          <li><%= link_to "Home", root_path %></li>
          <li><%= link_to "Help", help_path %></li>
          <% if signed_in? %>
            <li><%= link_to "Users", '#' %></li>
            <li id="fat-menu" class="dropdown">
              <a href="#" class="dropdown-toggle" data-toggle="dropdown">
                Account <b class="caret"></b>
              </a>
              <ul class="dropdown-menu">
                <li><%= link_to "Profile", current_user %></li>
                <li><%= link_to "Settings", edit_user_path(current_user) %></li>
                <li class="divider"></li>
                <li>
                  <%= link_to "Sign out", signout_path, method: "delete" %>
                </li>
              </ul>
            </li>
          <% else %>
            <li><%= link_to "Sign in", signin_path %></li>
          <% end %>
        </ul>
      </nav>
    </div>
  </div>
</header>
Листинг 9.7. Добавление ссылки “Settings”. app/views/layouts/_header.html.erb
Вадим Обозин
Вадим Обозин

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

Акбар Ахвердов
Акбар Ахвердов
Россия, г. Москва
Артём Зайцев
Артём Зайцев
Украина, ДНР