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

Микросообщения пользователей

< Лекция 9 || Лекция 10: 123456 || Лекция 11 >

Просмотр микросообщений

Хотя у нас еще нет способа создания микросообщений через веб — он появится лишь в Разделе 10.3.2 — это не остановит нас от их отображения (и тестирования этого отображения). Следуя по стопам Twitter, мы запланируем отображение микросообщений пользователя не на отдельной странице microposts index, а непосредственно на самой странице user show, как это показано на Рис. 10.4. Мы начнем с довольно простых ERb шаблонов для добавления отображения микросообщений в профиле пользователя, а затем мы добавим микросообщения в заполнитель образцов данных из Раздела 9.3.2 чтобы у нас было что отображать.

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

Рис. 10.4. Набросок страницы профиля пользователя с микросообщениями.

Как и в случае обсуждения машинерии входа в Разделе 8.2.1, Раздел 10.2.1 будет часто отправлять несколько элементов в стек на время, а затем выталкивать их оттуда один за другим. Если вы начнете увязать, будте терпеливы; у Раздела 10.2.2 хорошая развязка.

Дополнение страницы показывающей пользователя

Мы начнем с тестов для отображения микросообщений пользователя, которые мы будем создавать в интеграционных спеках для пользователей. Нашей стратегией будет создание пары фабричных микросообщений связанных с пользователем и последующей проверкой того что страница пользователя содержит контент каждого из микросообщений. Мы также проверим, что, как и на Рис. 10.4, также отображается общее количество микросообщений.

Мы можем создать микросообщения с помощью метода let, но, как и в Листинге 10.10, мы хотим чтобы ассоциация появилась незамедлительно, чтобы сообщения появились на странице пользователя. Для того чтобы достигнуть этого, мы используем вариант с let!:

let(:user) { FactoryGirl.create(:user) }
let!(:m1) { FactoryGirl.create(:micropost, user: user, content: "Foo") }
let!(:m2) { FactoryGirl.create(:micropost, user: user, content: "Bar") }

before { visit user_path(user) }

С таким определением микросообщений, мы можем протестировать их наличие на странице профиля пользователя с помощью кода в Листинге 10.16.

require 'spec_helper'

describe "User pages" do
  .
  .
  .
  describe "profile page" do
    let(:user) { FactoryGirl.create(:user) }
    let!(:m1) { FactoryGirl.create(:micropost, user: user, content: "Foo") }
    let!(:m2) { FactoryGirl.create(:micropost, user: user, content: "Bar") }

    before { visit user_path(user) }

    it { should have_content(user.name) }
    it { should have_title(user.name) }

    describe "microposts" do
      it { should have_content(m1.content) }
      it { should have_content(m2.content) }
      it { should have_content(user.microposts.count) }
    end
  end
  .
  .
  .
end
Листинг 10.16. Тесты для отображения микросообщений на странице пользователя. spec/requests/user_pages_spec.rb

Обратите внимание на то, что мы можем использовать метод count через ассоциацию:

user.microposts.count

Метод ассоциации count умен, и выполняет подсчет непосредственно в базе данных. В частности, он не вытягивает все микросообщения из базы данных и не вызывает затем length на получившемся массиве, так как это стало бы страшно неэффективно при увеличении числа микросообщений. Вместо этого, он просит базу данных подсчитать количество микросообщений с данным user_id. Кстати, в маловероятном случае при котором count все же будет узким местом в вашем приложении, вы можете сделать его еще быстрее с помощью counter cache.

Хотя тесты в Листинге 10.16 не позеленеют до Листинга 10.18, мы начнем работать с кодом приложения вставив список микросообщений в страницу профиля пользователя как это показано в Листинге 10.17.

<% provide(:title, @user.name) %>
<div class="row">
  .
  .
  .
  <aside>
    .
    .
    .
  </aside>
  <div class="span8">
    <% if @user.microposts.any? %>
      <h3>Microposts (<%= @user.microposts.count %>)</h3>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    <% end %>
  </div>
</div>
Листинг 10.17. Добавление микросообщений к странице показывающей пользователя. app/views/users/show.html.erb

Мы займемся списком микросообщений через мгновение, но есть несколько других вещей, которые необходимо отметить в первую очередь. В Листинге 10.17 мы применили if @user.microposts.any? (конструкцию, которую мы видели ранее в Листинге 7.24) которая дает нам уверенность в том, что пустой список не будет отображен если у пользователя нет микросообщений.

В Листинге 10.17 также обратите внимание на то, что мы превентивно добавили пагинацию для микросообщений

<%= will_paginate @microposts %>

Если вы сравните это с аналогичной строкой на странице списка пользователей в Листинге 9.33, вы увидите, что прежде мы имели просто

<%= will_paginate %>

Это работало потому что в контексте контроллера Users, will_paginate предполагает существование переменной экземпляра @users (которая, как мы видели в Разделе 9.3.3, должна принадлежать к классу ActiveRecord::Relation). В данном случае, поскольку мы все еще в контроллере Users, но хотим пагинировать микросообщения, а не пользователей, мы явно передаем @microposts переменную в will_paginate. Конечно, это означает, что мы должны будем определить такую переменную в user show действии (Листинг 10.19).

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

<h3>Microposts (<%= @user.microposts.count %>)</h3>

Как было отмечено, @user.microposts.count является аналогом метода User.count, за тем исключением, что он считает микросообщения принадлежащие данному пользователю через ассоциацию пользователь/микросообщения.

Наконец мы подошли к самому списку микросообщений:

<ol class="microposts">
  <%= render @microposts %>
</ol>

Этот код, использующий тег упорядоченного списка ol, отвечает за генерацию списка микросообщений, но, как вы можете видеть, он перекладывает тяжелую работу на партиал микросообщений. Мы видели в Разделе 9.3.4 что код

<%= render @users %>

автоматически рендерит каждого из пользователей в переменной @users с помощью партиала _user.html.erb. Аналогичным образом код

<%= render @microposts %>

делает тоже самое для микросообщений. Это означает, что мы должны определить партиал _micropost.html.erb (вместе с директорией представлений micropost), как это показано в Листинге 10.18.

<li>
  <span class="content"><%= micropost.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
  </span>
</li>
Листинг 10.18. Партиал для отображения одного микросообщения. app/views/microposts/_micropost.html.erb

При этом используется удивительный вспомогательный метод time_ago_in_words, чей эффект мы увидим в Разделе 10.2.2.

До сих пор, несмотря на определение всех соответствующих ERb шаблонов, тесты в Листинге 10.16 были провальными за неимением переменной @microposts. Мы можем заставить их пройти с кодом из Листинга 10.19.

class UsersController < ApplicationController
  .
  .
  .
  def show
    @user = User.find(params[:id])
    @microposts = @user.microposts.paginate(page: params[:page])
  end
  .
  .
  .
end
Листинг 10.19. Добавление переменной экземпляра @microposts в действие show контроллера Users. app/controllers/users_controller.rb

Обратим здесь внимание, насколько умен paginate — он работает даже с ассоциацией микросообщений, залезая в таблицу microposts и вытягивая оттуда нужную страницу микросообщений.

В этой точке мы можем взглянуть на нашу новую страницу профиля пользователя на рис. 10.5. Это довольно… печально. Конечно, это связано с тем, что в настоящее время у нас нет микросообщений. Пришло время изменить это.

Страница профиля пользователя с кодом для микросообщений, но без микросообщений.

Рис. 10.5. Страница профиля пользователя с кодом для микросообщений, но без микросообщений.

Образцы микросообщений

При всей проделанной работе по созданию шаблонов для микросообщений пользователя в Разделе 10.2.1, конец был довольно разочаровывающим. Мы можем исправить эту печальную ситуацию, добавив микросообщения во вноситель образцов данных из Раздела 9.3.2. Добавление образцов микросообщений для всех пользователей займет довольно много времени, поэтому сначала мы выберем только первые шесть пользователей3(т.e., пять пользователей с кастомными Gravatars, и один с дефолтным) используя :limit опцию метода User.all:4Проследите за своим log/development.log файлом если вам интересен SQL который генерирует этот метод.

users = User.all(limit: 6)

Затем мы сделаем 50 микросообщений для каждого пользователя (достаточно, для переполнения лимита пагинации, равного 30), сгенерируем образец содержимого для каждого микросообщения, используя удобный метод Lorem.sentence гема Faker.. (Faker::Lorem.sentence возвращает текст lorem ipsum; как отмечалось в Главе 6, lorem ipsum имеет увлекательную предысторию.) Получившийся в результате новый заполнитель образцов данных показан в Листинге 10.20.

namespace :db do
  desc "Fill database with sample data"
  task populate: :environment do
    .
    .
    .
    users = User.all(limit: 6)
    50.times do
      content = Faker::Lorem.sentence(5)
      users.each { |user| user.microposts.create!(content: content) }
    end
  end
end
Листинг 10.20. Добавление микросообщений к образцам данных. lib/tasks/sample_data.rake

Конечно, для генерации новых образцов данных мы должны запустить Рейк задачу db:populate:

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

Теперь мы в состоянии воспользоваться плодами наших трудов в Разделе 10.2.1 отобразив информацию для каждого микросообщеня.5Устройство гема Faker таково, что текст lorem ipsum случаен, поэтому контент ваших образцов микросообщений будет отличаться. Предварительные результаты показаны на рис. 10.6.

Профиль пользователя (/users/1) с неотстиленными микросообщениями.

Рис. 10.6. Профиль пользователя (/users/1) с неотстиленными микросообщениями.

Страница показанная на рис. 10.6 не имеет стилей для микросообщений, так что давайте их добавим (Листинг 10.21) и посмотрим что из этого вышло.6Для удобства, Листинг 10.21 на самом деле содержит весь CSS необходимый для этой главы. рис. 10.7, показывает страницу профиля пользователя для первого (вошедшего) пользователя, тогда как Рис. 10.8 показывает профиль для второго пользователя. Наконец, рис. 10.9 показывает вторую страницу микросообщений для первого пользователя с пагинационными ссылками внизу экрана. Во всех трех случаях, видно что каждое микросообщение отображается вместе со временем его создания (напр., "Posted 1 minute ago."); это работа метода time_ago_in_words из Листинга 10.18. Если вы подождете пару минут и перезагрузите страницу, вы увидите, как текст автоматически обновится в соответствии с новым временем.

.
.
.

/* microposts */

.microposts {
  list-style: none;
  margin: 10px 0 0 0;

  li {
    padding: 10px 0;
    border-top: 1px solid #e8e8e8;
  }
}
.content {
  display: block;
}
.timestamp {
  color: $grayLight;
}
.gravatar {
  float: left;
  margin-right: 10px;
}
aside {
  textarea {
    height: 100px;
    margin-bottom: 5px;
  }
}
Листинг 10.21. CSS для микросообщений (включая весь CSS для этой главы). app/assets/stylesheets/custom.css.scss
Профиль пользователя (/users/1) с микросообщениями.

Рис. 10.7. Профиль пользователя (/users/1) с микросообщениями.
Профиль другого пользователя, тоже с микросообщениями (/users/5).

Рис. 10.8. Профиль другого пользователя, тоже с микросообщениями (/users/5).
Пагинационные ссылки микросообщений (/users/1?page=2).

Рис. 10.9. Пагинационные ссылки микросообщений (/users/1?page=2).
< Лекция 9 || Лекция 10: 123456 || Лекция 11 >
Вадим Обозин
Вадим Обозин

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