Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора? |
Микросообщения пользователей
Предварительная реализация потока сообщений
Комментарий в конце Раздела 10.3.2 намекает на проблему: текущая Home страница не отображает микросообщений. Если вы хотите, вы можете убедиться что форма, показанная на рис. 10.11 работает, введя допустимое микросообщение и затем перейдя на страницу профиля чтобы увидеть сообщение, но это довольно громоздко. Было бы гораздо лучше иметь feed (поток, канал) микросообщений, который включал бы в себя микросообщения пользователя, как показано на рис. 10.13. (В Главе 11 мы обобщим этот канал (поток), включив микросообщения пользователей за которыми следит текущий пользователь.)
Так как каждый пользователь должен иметь поток сообщений, мы естественным образом пришли к feed методу в модели User. В конце концов, мы протестируем, что поток сообщений возвращает микросообщения пользователей, за которыми следит текущий пользователь, но пока мы просто протестируем, что feed метод включает в себя микросообщения текущего пользователя, но исключает сообщения других пользователей. Мы можем выразить эти требования в коде Листинга 10.35.
require 'spec_helper' describe User do . . . it { should respond_to(:microposts) } it { should respond_to(:feed) } . . . describe "micropost associations" do before { @user.save } let!(:older_micropost) do FactoryGirl.create(:micropost, user: @user, created_at: 1.day.ago) end let!(:newer_micropost) do FactoryGirl.create(:micropost, user: @user, created_at: 1.hour.ago) end . . . describe "status" do let(:unfollowed_post) do FactoryGirl.create(:micropost, user: FactoryGirl.create(:user)) end its(:feed) { should include(newer_micropost) } its(:feed) { should include(older_micropost) } its(:feed) { should_not include(unfollowed_post) } end end endЛистинг 10.35. Тесты для предварительной реализацией потока сообщений. spec/models/user_spec.rb
Эти тесты вводят (через булеву конвенцию RSpec) метод массива include?, который просто проверяет что массив включает данный элемент:9Изучение методов, таких как include? является одной из причин, по которым, как отмечалось в Разделе 1.1.1, я рекомендую читать книги о чистом Ruby после окончания этого учебника.
$ rails console >> a = [1, "foo", :bar] >> a.include?("foo") => true >> a.include?(:bar) => true >> a.include?("baz") => false
Этот пример показывает насколько гибкой является булевая конвенция RSpec; даже несмотря на то, что include уже является ключевым словом в Ruby (используется для включения модуля, как это можно увидеть в, например, Листинге 8.14), в этом контексте RSpec правильно угадывает что мы хотим протестировать включение в массив.
Мы можем организовать feed соответствующих микросообщений, выбрав все микросообщения с user_id равным id текущего пользователя, чего мы можем достигнуть используя where метод на модели Micropost, как это показано в Листинге 10.36.10См. Rails Guide Active Record Query Interface для того, чтобы узнать больше о where.
class User < ActiveRecord::Base . . . def feed # Это предварительное решение. См. полную реализацию в "Following users". Micropost.where("user_id = ?", id) end . . . endЛистинг 10.36. Предварительная реализация потока микросообщений. app/models/user.rb
Знак вопроса в
Micropost.where("user_id = ?", id)
гарантирует, что id корректно маскирован прежде чем быть включенным в лежащий в его основе SQL запрос, что позволит избежать серьезной дыры в безопасности называемой SQL инъекция. Атрибут id в данном случае просто целое число (т.e., self.id, уникальный ID пользователя), так что в этом случае опасности нет, но постоянное маскирование переменных, вводимых в SQL выражение является хорошей привычкой.
Внимательные читатели могли отметить в этой точке, что код в Листинге 10.36 по сути эквивалентен записи
def feed microposts end
Мы использовали код Листинга 10.36 вместо нее так как он генерализует гораздо более естественным образом полноценный поток микросообщений необходимый в Главе 11.
Для того чтобы протестировать отображение потока сообщений, мы вначале должны создать пару сообщений, а затем проверить что на странице присутствует элемент списка (li) для каждого из них (Листинг 10.37).
require 'spec_helper' describe "Static pages" do subject { page } describe "Home page" do . . . describe "for signed-in users" do let(:user) { FactoryGirl.create(:user) } before do FactoryGirl.create(:micropost, user: user, content: "Lorem ipsum") FactoryGirl.create(:micropost, user: user, content: "Dolor sit amet") sign_in user visit root_path end it "should render the user's feed" do user.feed.each do |item| expect(page).to have_selector("li##{item.id}", text: item.content) end end end end . . . endЛистинг 10.37. Тест рендеринга потока сообщений на странице Home. spec/requests/static_pages_spec.rb
Листинг 10.37 предполагает что каждый элемент потока сообщений имеет уникальный CSS id, так что
expect(page).to have_selector("li##{item.id}", text: item.content)
генерирует совпадение для каждого из них. (Обратите внимание что первый # в li##{item.id} является синтаксисом Capybara для CSS id, тогда как второй # является началом Рубишной интерполяции строк #{}.)
Для того чтобы использовать поток микросообщений в примере приложения, мы добавим переменную экземпляра @feed_items для (пагинированного) потока сообщений текущего пользователя как в Листинге 10.38, а затем добавим партиал потока сообщений (Листинг 10.39) к Home странице (Листинг 10.41). (Добавление тестов пагинаци остается в качестве упражнения; см. Раздел 10.5.)
class StaticPagesController < ApplicationController def home if signed_in? @micropost = current_user.microposts.build @feed_items = current_user.feed.paginate(page: params[:page]) end end . . . endЛистинг 10.38. Добавление переменной экземпляра потока сообщений к home действию. app/controllers/static_pages_controller.rb
<% if @feed_items.any? %> <ol class="microposts"> <%= render partial: 'shared/feed_item', collection: @feed_items %> </ol> <%= will_paginate @feed_items %> <% end %>Листинг 10.39. Партиал потока сообщений. app/views/shared/_feed.html.erb
Партиал потока сообщений перекладывает рендеринг элемента потока сообщений на партиал элемента потока сообщений с помощью кода
<%= render partial: 'shared/feed_item', collection: @feed_items %<
Здесь мы передаем параметр :collection с элементами потока сообщений, что заставляет render использовать данный партиал (’feed_item’ в данном случае) для рендеринга каждого элемента в коллекции. (Мы опустили параметр :partial в предыдущих рендерингах, используя запись, например, render ’shared/micropost’, но с :collection параметром этот синтаксис не работает.) Сам партиал элемента потока сообщений представлен в Листинге 10.40.
<li id="<%= feed_item.id %>"> <%= link_to gravatar_for(feed_item.user), feed_item.user %> <span class="user"> <%= link_to feed_item.user.name, feed_item.user %> </span> <span class="content"><%= feed_item.content %></span> <span class="timestamp"> Posted <%= time_ago_in_words(feed_item.created_at) %> ago. </span> </li>Листинг 10.40. Партиал для отдельно взятого элемента потока сообщений. app/views/shared/_feed_item.html.erb
Листинг 10.40 также добавляет CSS id для каждого элемента потока сообщений с помощью
<li id="<%= feed_item.id %>">
как того требует тест в Листинге 10.37.
Затем мы можем добавить поток сообщений на Home страницу посредством рендеринга партиала потока сообщений как обычно (Листинг 10.41). В результате поток сообщений отображается на Home странице, как и требовалось ( рис. 10.14).
<% if signed_in? %> <div class="row"> . . . <div class="span8"> <h3>Micropost Feed</h3> <%= render 'shared/feed' %> </div> </div> <% else %> . . . <% end %>Листинг 10.41. Добавление потока микросообщений к Home странице. app/views/static_pages/home.html.erb
На данный момент, создание новых микросообщений работает как надо, что показано на рис. 10.15. Однако есть одна тонкость: при неудачной отправке микросообщения Home страница ожидает переменную экземпляра @feed_items, таким образом провальная отправка в настоящее время не работает (что вы можете проверить запустив ваш набор тестов). Самым простым решением будет полное подавление потока сообщений через присвоение ему пустого массива, как это показано в Листинге 10.42.11К сожалению, возвращение пагинированного потока сообщений не работает в данном случае. Реализуйте это и кликните по пагинационной ссылке чтобы увидеть почему.
class MicropostsController < ApplicationController . . . def create @micropost = current_user.microposts.build(micropost_params) if @micropost.save flash[:success] = "Micropost created!" redirect_to root_url else @feed_items = [] render 'static_pages/home' end end . . . endЛистинг 10.42. Добавление (пустой) @feed_items переменной экземпляра к create действию. app/controllers/microposts_controller.rb
В этой точке предварительная реализация потока микросообщений должна работать, а набор тестов должен проходить:
$ bundle exec rspec spec/