Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора? |
Микросообщения пользователей
Уничтожение микросообщений
Последний кусок функционала, добавляемый к ресурсу Microposts это воможность уничтожения микросообщений. Как и с удалением пользователя (Раздел 9.4.2), мы будем делать это с помощью "delete" ссылок, как показано на рис. 10.16. В отличие от уничтожения пользователя, где право на удаление имели только администраторы, удаляющие ссылки будут работать только для пользователя, создавшего микросообщения.
![Набросок предварительной реализации потока сообщений со ссылками на удаление микросообщений.](/EDI/13_05_16_2/1463091622-23109/tutorial/1150/objects/10/files/10_16.png)
Рис. 10.16. Набросок предварительной реализации потока сообщений со ссылками на удаление микросообщений.
Нашим первым шагом является добавление удаляющей ссылки в партиал микросообщения как в Листинге 10.40, и пока мы в нем, мы добавим похожую ссылку к партиалу элемента потока из Листинга 10.40. Результат представлен в Листинге 10.43 и Листинге 10.44. (Эти два случая почти идентичны и устранение этого дублирования остается в качестве упражнения (Раздел 10.5).)
<li> <span class="content"><%= micropost.content %></span> <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. </span> <% if current_user?(micropost.user) %> <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" }, title: micropost.content %> <% end %> </li>Листинг 10.43.
<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> <% if current_user?(feed_item.user) %> <%= link_to "delete", feed_item, method: :delete, data: { confirm: "You sure?" }, title: feed_item.content %> <% end %> </li>Листинг 10.44. Партиал элемента потока микросообщений с добавленной ссылкой на удаление. app/views/shared/_feed_item.html.erb
Тест на удаление микросообщений использует Capybara для клика по ссылке "delete" и ожидает что количество Микросообщений уменьшится на 1 (Листинг 10.45).
require 'spec_helper' describe "Micropost pages" do . . . describe "micropost destruction" do before { FactoryGirl.create(:micropost, user: user) } describe "as correct user" do before { visit root_path } it "should delete a micropost" do expect { click_link "delete" }.to change(Micropost, :count).by(-1) end end end endЛистинг 10.45. Тест для действия destroy контроллера Microposts. spec/requests/micropost_pages_spec.rb
Код приложения также аналогичен коду для удаления пользователей из Листинга 9.46; главное отличие в том, что вместо использования предфильтра admin_user, в случае микросообщений мы используем предфильтр correct_user для проверки того, что текущий пользователь действительно имеет микросообщение с данным id. Код представлен в Листинге 10.46, а результаты уничтожения предпоследнего сообщения представлены на рис. 10.17.
class MicropostsController < ApplicationController before_action :signed_in_user, only: [:create, :destroy] before_action :correct_user, only: :destroy . . . def destroy @micropost.destroy redirect_to root_url end private def micropost_params params.require(:micropost).permit(:content) end def correct_user @micropost = current_user.microposts.find_by(id: params[:id]) redirect_to root_url if @micropost.nil? end endЛистинг 10.46. Действие destroy контроллера Microposts. app/controllers/microposts_controller.rb
В предфильтре correct_user обратите внимание на то, что мы ищем микросообщения через ассоциацию:
current_user.microposts.find_by(id: params[:id])
Это автоматически обеспечивает поиск лишь микросообщений принадлежащих текущему пользователю. В данном случае мы используем find_by вместо find так как последнее вызывает исключение в случае если микросообщение не существует вместо того, чтобы вернуть nil. Кстати, если вы хорошо знакомы с исключениями в Ruby, вы также можете написать фильтр correct_user вроде этого:
def correct_user @micropost = current_user.microposts.find(params[:id]) rescue redirect_to root_url end
Мы могли бы реализовать фильтр correct_user непосредственно через модель Micropost, например так:
@micropost = Micropost.find_by(id: params[:id]) redirect_to root_url unless current_user?(@micropost.user)
Это было бы эквивалентно коду в Листинге 10.46, но, как объяснял Wolfram Arnold с своем блоге Access Control 101 in Rails and the Citibank Hack, в целях безопасности, хорошей практикой является выполнение поиска только через ассоциацию.
С кодом в этом разделе наша модель Micropost и интерфейс завершены и набор тестов должен пройти:
$ bundle exec rspec spec/
Заключение
Добавив ресурс Microposts, мы почти закончили наш пример приложения. Все, что осталось, это добавить социальный слой, позволив пользователям следовать друг за другом. Мы узнаем как моделировать такие отношения пользователей и увидим полноценную реализацию потока сообщений в Главе 11.
Прежде чем продолжать, убедитесь что вы закоммитили и объединили ваши изменения если вы используете Git для контроля версий:
$ git add . $ git commit -m "Add user microposts" $ git checkout master $ git merge user-microposts $ git push
Вы также можете отправить приложение на Heroku. Поскольку модель данных изменилась из-за добавления таблицы microposts, вам также потребуется запустить миграцию продакшен базы данных:
$ git push heroku $ heroku pg:reset DATABASE $ heroku run rake db:migrate $ heroku run rake db:populate
Упражнения
Мы рассмотрели достаточно материала и теперь случился комбинаторный взрыв возможных расширений к нашему приложению. Вот лишь некоторые из многих возможностей:
- Добавить тесты для отображения количества микросообщений в сайдбаре (включая надлежащие плюрализации).
- Добавить тесты для пагинации микросообщений.
- Сделать рефакторинг Home страницы чтобы использовать отдельные партиалы для двух ветвей выражения if-else.
- Написать тест чтобы убедиться, что ссылки на удаление не появляются у микросообщений созданных не текущим пользователем.
- Удалить с помощью партиалов дублирование кода в удаляющих ссылках из Листинга 10.43 и Листинга 10.44.
- Сейчас очень длинные слова крушат наш шаблон, как это показано на рис. 10.18. Исправьте эту проблему с помощью хелпера wrap определенного в Листинге 10.47. Обратите внимание на использование метода raw для предотвращения маскирования Рельсами результирующего HTML, совместно с sanitize методом необходимым для предотвращения межсайтового скриптинга. Этот код также использует странно выглядящий, но полезный тернарный оператор (Блок 10.1).
- (сложное) Добавить JavaScript отображение к Home странице для обратного отсчета 140 знаков.
________________________________________
Блок 10.1.10 типов людей
В мире существует 10 типов людей: Те, кому нравится тернарный оператор, те, кому он не нравится, и те, кто не знает о нем. (Если вам случилось быть в третьей категории, скоро вы ее покинете.)
Когда вы много программируете, вы быстро узнаете, что одним из наиболее распространенных битов управления потоком, является что то вроде этого:
if boolean? do_one_thing else do_something_else end
Ruby, как и многие другие языки (включая C/C++, Perl, PHP, и Java), позволяет заменить это на гораздо более компактное выражение с помощью тернарного оператора (названного таким образом, потому что он состоит из трех частей):
boolean? ? do_one_thing : do_something_else
Вы можете также использовать тернарный оператор в качестве замены для назначения:
if boolean? var = foo else var = bar end
становится
var = boolean? ? foo : bar
Другим распространенным случаем использования является возвращаемое значение функции:
def foo do_stuff boolean? ? "bar" : "baz" end
оскольку Ruby неявно возвращает значение последнего выражения в функции, здесь метод foo возвращает "bar" или "baz" в зависимости от значения boolean?. Именно эта конструкция представлена в Листинге 10.47.
________________________________________
module MicropostsHelper def wrap(content) sanitize(raw(content.split.map{ |s| wrap_long_string(s) }.join(' '))) end private def wrap_long_string(text, max_width = 30) zero_width_space = "" regex = /.{1,#{max_width}}/ (text.length < max_width) ? text : text.scan(regex).join(zero_width_space) end endЛистинг 10.47. Хелпер для упаковки длинных слов. app/helpers/microposts_helper.rb