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

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

Образцы пользователей

В этом разделе мы дадим нашему одинокому образцу пользователя небольшую компанию. Конечно, чтобы создать достаточное количество пользователей, мы могли бы, использовать наш веб-браузер, для посещения страницы регистрации и создания новых пользователей по одному, но куда более продвинутым решением является использование Ruby (и Rake), чтобы сделать пользователей для нас.

Во-первых, мы добавим Faker гем в Gemfile, который позволит нам делать образцы пользователей с полу-реалистичными именами и адресами электронной почты (Листинг 9.28).

source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.2'
gem 'bootstrap-sass', '2.3.2.0'
gem 'bcrypt-ruby', '3.1.2'
gem 'faker', '1.1.2'
.
.
.
Листинг 9.28. обавление Faker гема в Gemfile.

Затем установите как обычно:

$ bundle install

Далее мы добавим Rake-задачу для создания образцов пользователей. Rake задачи живут в lib/tasks, и определяются с помощью пространства имен (в даном случае, :db), как видно в Листинге 9.29. (Это довольно продвинутый материал, так что не особо заморачивайтесь деталями.)

namespace :db do
  desc "Fill database with sample data"
  task populate: :environment do
    User.create!(name: "Example User",
                 email: "example@railstutorial.org",
                 password: "foobar",
                 password_confirmation: "foobar")
    99.times do |n|
      name  = Faker::Name.name
      email = "example-#{n+1}@railstutorial.org"
      password  = "password"
      User.create!(name: name,
                   email: email,
                   password: password,
                   password_confirmation: password)
    end
  end
end
Листинг 9.29. Rake задача для заполнения базы данных образцами пользователей. lib/tasks/sample_data.rake

Этот код определяет задачу db:populate которая создает образец пользователя с именем и адресом электронной почты, делая реплику нашего предыдущего пользователя, после чего делает еще 99 экземпляров. Строка

task populate: :environment do

обеспечивает Rake задаче доступ к локальному Rails окружению, включая модель User (и, следовательно, к User.create!). Здесь create! это метод очень похожий на create, за той лишь разницей, что он вызывает исключение (Раздел 6.1.4) при неудачном создании, вместо того чтобы тихо возвращать false. Эта крикливая конструкция упрощает отладку, помогая избежать тихих ошибок.

С пространством имен :db как в Листинге 9.29, мы можем вызвать Rake задачу следующим образом:

$ bundle exec rake db:reset
$ bundle exec rake db:populate
$ bundle exec rake test:prepare

После запуска Rake задачи, наше приложениее имеет 100 примеров пользователей, как это видно на рис. 9.9. (Я взял на себя смелость связать первые несколько образцов адресов с фотографиями так что не все изображения являются дефолтными картинками Gravatar.)

Страница списка пользователей /users с 100 образцов пользователей.

Рис. 9.9. Страница списка пользователей /users с 100 образцов пользователей.

Пагинация

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

В Rails есть несколько способов разбиения на страницы, мы будем использовать один из самых простых и надежных, он называется will_paginate. Для того, чтобы использовать его нам необходимо включить сам гем will_paginate, а также гем bootstrap-will_paginate, который конфигурирует will_paginate для использования пагинационных стилей предоставляемых Bootstrap. Обновленный Gemfile представлен в Листинге 9.30.

source 'https://rubygems.org'
ruby '2.0.0'
#ruby-gemset=railstutorial_rails_4_0

gem 'rails', '4.0.2'
gem 'bootstrap-sass', '2.3.2.0'
gem 'bcrypt-ruby', '3.1.2'
gem 'faker', '1.1.2'
gem 'will_paginate', '3.0.4'
gem 'bootstrap-will_paginate', '0.0.9'
.
.
.
Листинг 9.30. Включение will_paginate в Gemfile.

Затем запустите bundle install:

Затем запустите bundle install:

Вам также следует перезагрузить веб-сервер для того чтобы убедиться, что новые гемы были загружены как следует.

Поскольку гем will_paginate широко распространен, нам нет необходимости тестировать его, так что мы можем применить несколько облегченный подход. Во-первых, мы протестируем наличие div с CSS классом "pagination", который выводится гемом will_paginate. Затем мы проверим что на первой странице результатов представлены правильные пользователи. Для этого нам потребуется использовать метод paginate, о котором мы вскоре узнаем подробнее.

Как и прежде, мы будем использовать Factory Girl для имитации пользователей, но мы тут же натыкаемся на проблему: адреса электронной почты пользователей должны быть уникальными, что, как представляется, требует создания более чем 30 пользователей вручную — ужасно муторная работа. К тому же, при тестировании выводимого списка пользователей было бы удобно, если бы у них были разные имена. К счастью, Factory Girl предвидела этот вопрос, и обеспечила последовательность (цикл) для ее решения. Наша оригинальная фабрика (Листинг 7.8) хард-кодила имя и адрес электронной почты:

FactoryGirl.define do
  factory :user do
    name     "Michael Hartl"
    email    "michael@example.com"
    password "foobar"
    password_confirmation "foobar"
  end
end

Вместо этого мы можем организовать последовательность имен и адресов электронной почты с помощью метода sequence:

factory :user do
  sequence(:name)  { |n| "Person #{n}" }
  sequence(:email) { |n| "person_#{n}@example.com"}
  .
  .
  .

Здесь sequence принимает символ соответствующий выбранному атрибуту (такому как :name) и блок с одной переменной, которую мы назвали n. При последующих вызовах FactoryGirl метод,

FactoryGirl.create(:user)

Переменная блока n автоматически увеличивается, таким образом, именем первого пользователя будет "Person 1", а адресом электронной почты - "person_1@example.com", второй пользователь получит имя "Person 2" и адрес электронной почты "person_2@example.com", и т.д.. Полный код представлен в Листинге 9.31.

FactoryGirl.define do
  factory :user do
    sequence(:name)  { |n| "Person #{n}" }
    sequence(:email) { |n| "person_#{n}@example.com"}
    password "foobar"
    password_confirmation "foobar"
  end
end
Листинг 9.31. Определение последовательности (цикла) Factory Girl. spec/factories.rb

Применив идею фабричных последовательностей, мы можем сделать 30 пользователей в наших тестах, чего (как мы увидим) будет достаточно для вызова пагинации:

before(:all) { 30.times { FactoryGirl.create(:user) } }
after(:all)  { User.delete_all }

Обратите здесь внимание на применение before(:all), который гарантирует создание образцовых пользователей единожды перед всеми тестами блока. Это оптимизирует скорость прохождения тестов, поскольку создание 30 пользователей может быть медленным на некоторых системах. Мы используем тесно связанный метод after(:all) для удаления прользователей по завершении.

Тесты на появление пагинационного div и наличие правильных пользователей представлены в Листинге 9.32. Обратите внимание на замену массива User.all из Листинга 9.22 на User.paginate(page: 1), который (как мы вскоре увидим) вытягивает первую страницу пользователей из базы данных. Обратите также внимание на то, что Листинг 9.32 использует before(:each) для того чтобы подчеркнуть контраст с before(:all).

require 'spec_helper'

describe "User pages" do

  subject { page }

  describe "index" do
    let(:user) { FactoryGirl.create(:user) }
    before(:each) do
      sign_in user
      visit users_path
    end

    it { should have_title('All users') }
    it { should have_content('All users') }

    describe "pagination" do

      before(:all) { 30.times { FactoryGirl.create(:user) } }
      after(:all)  { User.delete_all }

      it { should have_selector('div.pagination') }

      it "should list each user" do
        User.paginate(page: 1).each do |user|
          expect(page).to have_selector('li', text: user.name)
        end
      end
    end
  end
  .
  .
  .
end
Листинг 9.32. Тесты для пагинации. spec/requests/user_pages_spec.rb

Для того чтобы подключить пагинацию нам нужно добавить немного кода сообщающего Rails о необходимости пагинировать пользователей в представлении index и нам необходимо заменить User.all в index действии на объект который знает о пагинации. Мы начнем с добавления специального метода will_paginate в представление (Листинг 9.33); мы вскоре увидим почему код был добавлен сверху и снизу списка пользователей.

<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 52 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

<%= will_paginate %>
Листинг 9.33. Страница списка пользователей с пагинацией. app/views/users/index.html.erb

Метод will_paginate немного волшебный; внутри представления users он автоматически ищет объект @users, а затем отображает пагинационные ссылки для доступа к остальным страницам. Представление в Листинге 9.33 пока не работает - из-за того что на данный момент @users содержит результаты User.all (Листинг 9.23), в то время как will_paginate требует чтобы мы пагинировали результаты явно используя метод paginate:

$ rails console
>> User.paginate(page: 1)
  User Load (1.5ms)  SELECT "users".* FROM "users" LIMIT 30 OFFSET 0
   (1.7ms)  SELECT COUNT(*) FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1,...

Обратите внимание, что paginate принимает в качестве аргумента хэш с ключом :page и значением, равным запрашиваемой странице. User.paginate вытягивает пользователей из базы данных по одному куску за раз (30 по умолчанию), основываясь на параметре :page. Так, например, стр. 1 содержит пользователей с 1 по 30, стр. 2 это пользователи 31–60, и т.д.. Если страница является nil, paginate просто возвращает первую страницу.

Мы можем разбить список пользователей на страницы используя paginate вместо all в index действии (Листинг 9.34). Здесь :page параметр приходит из params[:page] который will_paginate сгенерировал автоматически.

class UsersController < ApplicationController
  before_action :signed_in_user, only: [:index, :edit, :update]
  .
  .
  .
  def index
    @users = User.paginate(page: params[:page])
  end
  .
  .
  .
end
Листинг 9.34. Пагинация пользователей в index действии. app/controllers/users_controller.rb

Страница списка пользователей теперь должна работать так как это показано на рис. 9.10. (На некоторых системах в этой точке может потребоваться перезапуск Rails сервера.) Так как мы включили will_paginate сверху и снизу списка пользователей, ссылки на страницы появились в обоих местах.

Страница списка пользователей /users с пагинацией.

Рис. 9.10. Страница списка пользователей /users с пагинацией.

Если теперь кликнуть по любой 2 или Next ссылке, вы получите вторую страницу с результатами, как это показано на рис. 9.11.

Страница 2 списка пользователей (/users?page=2).

Рис. 9.11. Страница 2 списка пользователей (/users?page=2).

Вам также следует проверить что тесты проходят:

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

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

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