Статьи

Обмен сообщениями с Rails и Mailboxer

красный почтовый ящик

Недавно я написал серию статей о создании приложений для чата: мини-чат с Rails , мини-чат в реальном времени с Rails и Faye и мини-чат с Rails и событиями, отправленными на сервер . Эти посты приобрели некоторую популярность среди читателей, и я получил много отзывов в комментариях, а также по электронной почте. Это здорово, и еще раз хочу поблагодарить вас, ребята, за то, что поделились своими мыслями.

Один из читателей упомянул Mailboxer , отличное решение для реализации систем обмена сообщениями. Итак, я решил исследовать это и поделиться результатами с вами.

Mailboxer — это драгоценный камень Rails, который является частью фреймворка social_stream для построения социальных сетей. Это универсальная система обмена сообщениями, которая позволяет любой модели действовать «сообщаемой», оснащая ее несколькими универсальными методами. С помощью Mailboxer вы можете создавать разговоры с одним или несколькими получателями (сообщения организованы в папки — sentbox, inbox, trash) и отправлять уведомления по электронной почте. Можно даже отправлять сообщения между разными моделями и добавлять вложения! Единственным недостатком является отсутствие документации, поэтому я надеюсь, что этот пост будет полезен.

Мы собираемся обсудить демонстрационное приложение, которое:

  • Использует базовую аутентификацию с Devise
  • Позволяет пользователям управлять аватарами с Gravatar
  • Интегрирует почтовый ящик
  • Создает графический интерфейс для начала новых бесед, а также ответа на существующие (с использованием стилей Bootstrap и плагина Chosen jQuery)
  • Отображает папки и позволяет легко переключаться между ними
  • Позволяет помечать разговоры как прочитанные, уничтожать разговоры и восстанавливать их. Это тоже очистит мусорное ведро.
  • Настраивает почтовые уведомления

Rails 4 будет использоваться для этой демонстрации, но почти такое же решение может быть реализовано с Rails 3.2 (версия 3.1 больше не поддерживается Mailboxer).

Исходный код доступен на GitHub .

Рабочую демонстрацию можно найти по адресу sitepoint-mailboxer.herokuapp.com .

Подготовка приложения

Предположим, нам нужно создать систему личных сообщений для внутреннего использования, где сотрудники могут обсуждать разные темы. Эта система должна позволять пользователям создавать разговоры с неограниченным числом получателей, предоставлять систему уведомлений и разрешать удаление устаревших разговоров.

Давайте назовем это Synergy и начнем с создания нового приложения на Rails без набора тестов по умолчанию:

$ rails new Synergy -T 

Перетащите эти драгоценные камни в свой Gemfile (я собираюсь придерживаться Bootstrap, но вы можете использовать любой другой CSS-фреймворк, использовать свой собственный дизайн или полностью пропустить предварительную настройку сайта):

Gemfile

 [...] gem 'bootstrap-sass' gem 'bootstrap-will_paginate' gem 'will_paginate' [...] 

Бегать

 $ bundle install 

и вставьте файлы Bootstrap, если хотите следовать:

application.css.scss

 @import 'bootstrap'; @import 'bootstrap/theme'; 

Далее немного подправим макет:

макеты / application.html.erb

 [...] <div class="container"> <% flash.each do |key, value| %> <div class="alert alert-<%= key %>"> <%= value %> </div> <% end %> <div class="page-header"> <h1><%= yield :page_header %></h1> </div> <%= yield %> </div> [...] 

Давайте также добавим вспомогательный метод для простой визуализации заголовка страницы:

application_helper.rb

 [...] def page_header(text) content_for(:page_header) { text.to_s } end [...] 

Аутентификация

Перед реализацией функции обмена сообщениями нам нужна модель, которая будет сообщать. Создайте модель User :

 $ rails g model User name:string $ rake db:migrate 

Вы можете использовать любой тип аутентификации, но мне нравится Devise . Базовая настройка Devise очень проста, и есть множество документов, которые помогут вам в дальнейшей настройке.

Бросьте новый драгоценный камень:

Gemfile

 [...] gem 'devise' [...] 

и установить его:

 $ bundle install 

Теперь мы можем воспользоваться генератором кода Devise, чтобы поработать за нас:

 $ rails generate devise:install 

Обязательно прочитайте сообщение после установки, чтобы выполнить некоторые дополнительные шаги. В частности, вам нужно настроить параметр config.action_mailer.default_url_options для разработки и производства, так как он будет использоваться для отправки электронных писем пользователям (например, чтобы помочь им восстановить забытые пароли). Обратите внимание, что электронное письмо не будет отправлено в процессе разработки, если вы не установите config.action_mailer.perform_deliveries = true в средах / development.rb .

Вот несколько примеров того, как настроить ActionMailer.

Когда вы будете готовы, выполните следующую команду, чтобы создать модель User с Devise:

 $ rails generate devise User $ rake db:migrate 

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

Наконец, беги

 $ rails generate devise:views 

скопировать взгляды Devise непосредственно в ваш проект, чтобы их можно было немного изменить. Пользователи смогут менять свои имена, поэтому добавьте новое поле в форму регистрации:

просмотров / Завещание / регистрация / new.html.erb

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

Перетащите тот же код в views / devise / registrations / edit.html.erb (или реорганизуйте его в частичное), чтобы пользователи могли указать свое имя при регистрации, а затем отредактировать его.

Это Rails 4, поэтому strong_params в игре. Разрешить передачу нового параметра :name :

application_controller.rb

 [...] before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters devise_parameter_sanitizer.for(:sign_up) << :name devise_parameter_sanitizer.for(:account_update) << :name end 

devise_controller? Метод предоставлен Devise. Здесь мы просто разрешаем атрибут :name для создания и редактирования учетной записи. Если вы забудете это сделать, пользователи не смогут установить свои имена.

На этом этапе вы также можете немного стилизовать представления. Я не буду описывать этот шаг, так как он не слишком сложен и сильно зависит от вашей настройки (используете ли вы Bootstrap или нет). Если вы решили использовать Bootstrap, то флэш-сообщения, сгенерированные Devise, не будут стилизованы. Чтобы это исправить, используйте метод SASS @extend , например так:

application.css.scss

 [...] .alert-notice { @extend .alert-success; } .alert-alert { @extend .alert-warning; } 

Интеграция почтового ящика

Отлично, наконец-то мы готовы приступить к основной задаче — интеграции и настройке Mailboxer.

Прежде всего, добавьте новый драгоценный камень:

Gemfile

 [...] gem "mailboxer" [...] 

и установить его:

 $ bundle install 

Сгенерируйте и примените все необходимые миграции и создайте файл инициализатора:

 $ rails g mailboxer:install $ rake db:migrate 

Взгляните на config / initializers / mailboxer.rb, чтобы увидеть, какие опции вы можете изменить. Пока оставим этот файл как есть — позже мы настроим отправку уведомлений по электронной почте.

Наша модель требует небольшого изменения, чтобы оснастить ее функциональностью Mailboxer:

модели / user.rb

 [...] acts_as_messageable [...] 

Отображение разговоров

Как предлагается в этом небольшом руководстве по созданию графического интерфейса для почтового ящика, оптимальный способ — создать два контроллера: один для сообщений и один для разговоров. Отдельные сообщения сгруппированы в разговоры. Позже вы увидите, что разговоры могут быть разных типов.

Начните с создания нового контроллера для разговоров:

conversations_controller.rb

 class ConversationsController < ApplicationController before_action :authenticate_user! before_action :get_mailbox def index @conversations = @mailbox.inbox.paginate(page: params[:page], per_page: 10) end private def get_mailbox @mailbox ||= current_user.mailbox end end 

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

authenticate_user! Метод является частью разработки. Мы хотим, чтобы только аутентифицированные пользователи before_action доступ к нашему приложению, поэтому оно установлено как before_action . Если пользователь не аутентифицирован, он будет перенаправлен на страницу входа.

Как вы видите, я также использую метод paginate предоставленный will_paginate . Стиль для нумерации страниц предоставляется bootstrap-will_paginate .

Добавьте несколько маршрутов (другие методы контроллера будут добавлены в ближайшее время):

конфиг / routes.rb

 [...] resources :conversations, only: [:index, :show, :destroy] [...] 

Теперь мнение:

просмотров / разговоры / index.html.erb

 <% page_header "Your Conversations" %> <ul class="list-group"> <%= render partial: 'conversations/conversation', collection: @conversations %> </ul> <%= will_paginate %> 

page_header — наш вспомогательный метод, созданный ранее. will_paginate отображает элементы управления разбиением на страницы (только если существует более одной страницы). Мы могли бы написать его как will_paginate @conversations но гем достаточно умен, чтобы понять, что мы хотим разбить на страницы в этом случае (соглашение о конфигурации!).

Мы должны указать partial аргумент для render потому что @conversations является экземпляром @conversations Mailboxer::Conversation::ActiveRecord_Relation и поэтому Rails по умолчанию будет искать _conversation расположенную в _conversation

Теперь актуальный частичный:

просмотров / разговоры / _conversation.html.erb

 <li class="list-group-item clearfix"> <%= link_to conversation.subject, conversation_path(conversation) %> </li> 

Каждый разговор имеет тему и несколько сообщений, которые будут отображаться на странице шоу . CSS-класс .clearfix скоро потребуется.

Добавьте меню в макет:

макеты / application.html.erb

 [...] <body> <nav class="navbar navbar-inverse"> <div class="container"> <div class="navbar-header"> <%= link_to 'Synergy', root_path, class: 'navbar-brand' %> </div> <ul class="nav navbar-nav"> <% if user_signed_in? %> <li><%= link_to 'Edit Profile', edit_user_registration_path %></li> <li><%= link_to 'Your Conversations', conversations_path %></li> <li><%= link_to 'Log Out', destroy_user_session_path, method: :delete %></li> <% else %> <li><%= link_to 'Log In', new_user_session_path %></li> <% end %> </ul> </div> </nav> </body> [...] 

Пользователь user_signed_in? Метод, как и большинство маршрутов, предоставляются Devise.

Следующее действие шоу :

conversations_controller.rb

 class ConversationsController < ApplicationController before_action :authenticate_user! before_action :get_mailbox before_action :get_conversation, except: [:index] def show end private def get_conversation @conversation ||= @mailbox.conversations.find(params[:id]) end end 

Я добавил новое before_action и before_action существующее.

Вы, вероятно, знаете, что метод find вызывает исключение, когда запись не найдена. Это то, что мы хотим, но исключение следует спасти. Для простоты давайте используем метод rescue_from :

application_controller.rb

 [...] rescue_from ActiveRecord::RecordNotFound do flash[:warning] = 'Resource not found.' redirect_back_or root_path end def redirect_back_or(path) redirect_to request.referer || path end [...] 

Мы просто перенаправляем пользователя обратно с предупреждением. Если поле referer не установлено (например, если пользователь установил надстройку для очистки этого поля), они перенаправляются в root_path .

Теперь мнение:

просмотров / разговоры / show.html.erb

 <% page_header "Conversation" %> <div class="panel panel-default"> <div class="panel-heading"><%= @conversation.subject %></div> <div class="panel-body"> <div class="messages"> <% @conversation.receipts_for(current_user).each do |receipt| %> <% message = receipt.message %> <%= message.sender.name %> says at <%= message.created_at.strftime("%-d %B %Y, %H:%M:%S") %> <%= message.body %> <% end %> </div> </div> </div> 

Мы отрисовываем каждое сообщение, показывая имя отправителя, дату создания и тело. Давайте немного .messages контейнер .messages чтобы он не стал слишком высоким:

application.css.scss

 [...] .messages { max-height: 400px; overflow-y: auto; margin-bottom: 1em; margin-top: 1em; } 

Хорошо, некоторые базовые представления присутствуют, однако нам все еще не хватает важных частей приложения:

  • Пользователь должен знать, с кем он общается
  • Пользователи должны иметь возможность начать новые разговоры
  • Пользователи должны иметь возможность отвечать на разговоры
  • Sentbox и мусор должны отображаться на странице разговоров
  • Пользователи должны иметь возможность отмечать разговоры как прочитанные

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

Хотя это не относится к почтовому ящику, я подумал, что показ аватаров сделает наше приложение красивее. Однако разрешить пользователям загружать свои аватары непосредственно в приложение было бы излишним, поэтому давайте использовать Gravatar и гравитационный тег изображения для его интеграции с Rails.

Поместите новый драгоценный камень в Gemfile:

Gemfile

 [...] gem 'gravatar_image_tag' [...] 

и беги

 $ bundle install 

Также добавьте вспомогательный метод для простой визуализации аватаров:

application_helper.rb

 [...] def gravatar_for(user, size = 30, title = user.name) image_tag gravatar_image_url(user.email, size: size), title: title, class: 'img-rounded' end [...] 

Создайте отдельный фрагмент для отображения аватаров участников разговора (кроме текущего пользователя):

просмотров / разговоры / _participants.html.erb

 <% conversation.participants.each do |participant| %> <% unless participant == current_user %> <%= gravatar_for participant %> <% end %> <% end %> 

Измените следующие представления:

просмотров / разговоры / show.html.erb

 <% page_header "Conversation" %> <p>Chatting with <%= render 'conversations/participants', conversation: @conversation %> </p> <div class="panel panel-default"> <div class="panel-heading"><%= @conversation.subject %></div> <div class="panel-body"> <div class="messages"> <% @conversation.receipts_for(current_user).each do |receipt| %> <div class="media"> <% message = receipt.message %> <div class="media-left"> <%= gravatar_for message.sender, 45, message.sender.name %> </div> <div class="media-body"> <h6 class="media-heading"><%= message.sender.name %> says at <%= message.created_at.strftime("%-d %B %Y, %H:%M:%S") %></h6> <%= message.body %> </div> </div> <% end %> </div> </div> </div> 

просмотров / разговоры / _conversation.html.erb

 <li class="list-group-item clearfix"> [...] <p><%= render 'conversations/participants', conversation: conversation %></p> </li> 

Пока мы здесь, отобразите последнее сообщение разговора и дату его создания:

просмотров / разговоры / _conversation.html.erb

 <li class="list-group-item clearfix"> [...] <p><%= render 'conversations/participant', conversation: conversation %></p> <p><%= conversation.last_message.body %> <small>(<span class="text-muted"><%= conversation.last_message.created_at.strftime("%-d %B %Y, %H:%M:%S") %></span>)</small></p> </li> 

Мы закончили с аватарами. Настало время разрешить пользователям начинать разговоры.

Создание разговоров

Создание разговора на самом деле означает создание нового сообщения с указанием темы (хотя это необязательно). Это означает, что потребуется новый контроллер:

messages_controller.rb

 class MessagesController < ApplicationController before_action :authenticate_user! def new end def create recipients = User.where(id: params['recipients']) conversation = current_user.send_message(recipients, params[:message][:body], params[:message][:subject]).conversation flash[:success] = "Message has been sent!" redirect_to conversation_path(conversation) end end 

В действии create найдите массив пользователей (хранится в params['recipients'] send_message params['recipients'] ) и используйте метод send_message , передавая получателей, тело и тему. Позже мы включим уведомления по электронной почте, чтобы пользователи знали, когда получено новое сообщение.

Теперь мнение:

просмотров / сообщений / new.html.erb

 <% page_header "Start Conversation" %> <%= form_tag messages_path, method: :post do %> <div class="form-group"> <%= label_tag 'message[subject]', 'Subject' %> <%= text_field_tag 'message[subject]', nil, class: 'form-control', required: true %> </div> <div class="form-group"> <%= label_tag 'message[body]', 'Message' %> <%= text_area_tag 'message[body]', nil, cols: 3, class: 'form-control', required: true %> </div> <div class="form-group"> <%= label_tag 'recipients', 'Choose recipients' %> <%= select_tag 'recipients', recipients_options, multiple: true, class: 'form-control' %> </div> <%= submit_tag 'Send', class: 'btn btn-primary' %> <% end %> 

recipients_options — это вспомогательный метод, который нам нужно создать:

messages_helper.rb

 module MessagesHelper def recipients_options s = '' User.all.each do |user| s << "<option value='#{user.id}'>#{user.name}</option>" end s.html_safe end end 

Не забудьте про маршруты:

конфиг / routes.rb

 [...] resources :messages, only: [:new, :create] [...] 

Давайте представим ссылку «Начать разговор» на странице указаний # бесед :

просмотров / разговоры / index.html.erb

 <% page_header "Your Conversations" %> <p><%= link_to 'Start conversation', new_message_path, class: 'btn btn-lg btn-primary' %></p> [...] 

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

Однако выбирать получателей не очень удобно. В настоящее время отображается базовое поле выбора, поэтому при наличии большого количества пользователей поиск кого-либо в списке может быть утомительной задачей. Мы можем улучшить это поле с помощью некоторых суперспособностей, используя Chosen , плагин jQuery, который делает выбор более удобным для пользователя. Существует драгоценный камень selected -rails, который облегчает интеграцию этого плагина в приложение Rails.

Добавьте этот драгоценный камень в свой Gemfile:

Gemfile

 [...] gem 'chosen-rails' [...] 

Мне также пришлось указать версии для sass-rails и coffee-rails , поскольку я получал ошибки, связанные с файлом application.css.scss , который является известной ошибкой ):

Gemfile

 [...] gem 'chosen-rails' gem 'sass-rails', '~> 4.0.5' gem 'coffee-rails', '~> 4.1.0' gem 'jquery-turbolinks' [...] 

Также я использую гем jquery-turbolinks для возврата события загрузки страницы jQuery по умолчанию при использовании Turbolinks.

Не забудь бежать

 $ bundle install 

и добавьте Chosen в application.js и application.css.scss :

JavaScripts / application.js

 [...] //= require jquery.turbolinks //= require chosen-jquery [...] 

таблицы стилей / application.css.scss

 [...] @import 'chosen'; [...] 

Теперь давайте добавим класс .chosen-it к нашему тегу select:

просмотров / сообщений / new.html.erb

 [...] <div class="form-group"> <%= label_tag 'recipients', 'Choose recipients' %> <%= select_tag 'recipients', recipients_options, multiple: true, class: 'form-control chosen-it' %> </div> [...] 

и экипировать все элементы этого класса магией Чосена:

JavaScripts / messages.coffee

 jQuery -> $('.chosen-it').chosen() 

JavaScripts / application.js

 [...] //= require messages [...] 

Теперь перезагрузите сервер, перейдите к беседам / новым и вот новый блестящий тег выбора. Это намного удобнее в использовании, не так ли?

Мы могли бы пойти дальше и отобразить аватары вместе с именами пользователей внутри тега select. Существует расширение Image-Select для Chosen. Просто подключите файлы ImageSelect.jquery.js и ImageSelect.css в свой проект и укажите их в файлах application.js и application.css.scss соответственно. Затем немного измените вспомогательный метод:

messages_helper.rb

 module MessagesHelper def recipients_options s = '' User.all.each do |user| s << "<option value='#{user.id}' data-img-src='#{gravatar_image_url(user.email, size: 50)}'>#{user.name}</option>" end s.html_safe end end 

Затем перезагрузите сервер и проверьте результаты. Очень круто!

Отвечая на разговор

Теперь пользователи могут создавать разговоры, но нет возможности ответить! Чтобы это исправить, нам нужна другая форма и метод контроллера, а также новый маршрут:

просмотров / разговоры / show.html.erb

 [...] <%= form_tag reply_conversation_path(@conversation), method: :post do %> <div class="form-group"> <%= text_area_tag 'body', nil, cols: 3, class: 'form-control', placeholder: 'Type something...', required: true %> </div> <%= submit_tag "Send Message", class: 'btn btn-primary' %> <% end %> 

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

conversations_controller.rb

 [...] def reply current_user.reply_to_conversation(@conversation, params[:body]) flash[:success] = 'Reply sent' redirect_to conversation_path(@conversation) end [...] 

Метод reply_to_conversation делает это несложно. Он принимает разговор для ответа, текст сообщения, необязательную тему и несколько других аргументов. Обратите внимание, что если разговор был перенесен в корзину (который мы вскоре рассмотрим), он будет восстановлен по умолчанию. Взгляните на исходный код для получения дополнительной информации.

Теперь маршрут:

конфиг / routes.rb

 [...] resources :conversations, only: [:index, :show, :destroy] do member do post :reply end end [...] 

Очень хорошо, наша базовая система чата запущена и работает!

Реализация Sentbox и Trash

В настоящее время мы показываем только входящие сообщения пользователя. Тем не менее, это хорошая идея для отображения папок sentbox и trash.

Вероятно, самый простой способ пометить какую папку для рендеринга — использовать параметр GET, поэтому давайте настроим контроллер соответствующим образом:

conversations_controller.rb

 [...] before_action :get_box, only: [:index] def index if @box.eql? "inbox" @conversations = @mailbox.inbox elsif @box.eql? "sent" @conversations = @mailbox.sentbox else @conversations = @mailbox.trash end @conversations = @conversations.paginate(page: params[:page], per_page: 10) end private def get_box if params[:box].blank? or !["inbox","sent","trash"].include?(params[:box]) params[:box] = 'inbox' end @box = params[:box] end [...] 

Новый приватный метод get_box введен для получения запрошенной папки.

На взгляд. Если вы используете Bootstrap, я предлагаю использовать вертикальные навигационные таблетки для отображения списка папок. Также текущая папка должна быть выделена. Создайте вспомогательный метод для этого:

conversations_helper.rb

 module ConversationsHelper def mailbox_section(title, current_box, opts = {}) opts[:class] = opts.fetch(:class, '') opts[:class] += ' active' if title.downcase == current_box content_tag :li, link_to(title.capitalize, conversations_path(box: title.downcase)), opts end end 

Этот метод получает заголовок ссылки (которая также используется для указания параметра GET), текущей открытой папки и хеша с параметрами, передаваемыми непосредственно методу content_tag . Затем проверьте, есть ли в хэше opts ключ class . Если нет, установите для него пустую строку, а затем добавьте active класс, если это текущее поле.

Изменить вид:

просмотров / разговоры / index.html.erb

 <% page_header "Your Conversations" %> <div class="row"> <div class="col-sm-3"> <ul class="nav nav-pills nav-stacked"> <%= mailbox_section 'inbox', @box %> <%= mailbox_section 'sent', @box %> <%= mailbox_section 'trash', @box %> </ul> </div> <div class="col-sm-9"> <ul class="list-group"> <%= render partial: 'conversations/conversation', collection: @conversations %> </ul> <%= will_paginate %> </div> </div> 

Следующим шагом является добавление кнопки «Добавить в корзину» для каждого разговора, который еще не был удален. Для потерянных разговоров должна отображаться кнопка «Восстановить».

просмотров / разговоры / _conversation.html.erb

 <li class="list-group-item clearfix"> <%= link_to conversation.subject, conversation_path(conversation) %> <div class="btn-group-vertical pull-right"> <% if conversation.is_trashed?(current_user) %> <%= link_to 'Restore', restore_conversation_path(conversation), class: 'btn btn-xs btn-info', method: :post %> <% else %> <%= link_to 'Move to trash', conversation_path(conversation), class: 'btn btn-xs btn-danger', method: :delete, data: {confirm: 'Are you sure?'} %> <% end %> <% end %> </div> <p><%= render 'conversations/participant', conversation: conversation %></p> <p><%= conversation.last_message.body %> <small>(<span class="text-muted"><%= conversation.last_message.created_at.strftime("%-d %B %Y, %H:%M:%S") %></span>)</small></p> </li> 

Соответствующие методы:

conversations_controller.rb

 [...] before_action :authenticate_user! before_action :get_mailbox before_action :get_conversation, except: [:index] before_action :get_box, only: [:index] [...] def destroy @conversation.move_to_trash(current_user) flash[:success] = 'The conversation was moved to trash.' redirect_to conversations_path end def restore @conversation.untrash(current_user) flash[:success] = 'The conversation was restored.' redirect_to conversations_path end [...] 

Еще раз я немного подправил перед действиями, чтобы они происходили только при необходимости. move_to_trash и untrash — это два метода, представленных Mailboxer, и они довольно untrash .

Теперь маршруты:

конфиг / routes.rb

 [...] resources :conversations, only: [:index, :show, :destroy] do member do post :restore end end [...] 

Как насчет кнопки «Пустой мусор». Легко:

просмотров / разговоры / index.html.erb

 [...] <div class="col-sm-9"> <% if @box == 'trash' %> <p><%= link_to 'Empty trash', empty_trash_conversations_path, class: 'btn btn-danger', method: :delete, data: {confirm: 'Are you sure?'} %></p> <% end %> <ul class="list-group"> <%= render partial: 'conversations/conversation', collection: @conversations %> </ul> <%= will_paginate %> </div> [...] 

Соответствующий метод:

conversations_controller.rb

 before_action :get_conversation, except: [:index, :empty_trash] [...] def empty_trash @mailbox.trash.each do |conversation| conversation.receipts_for(current_user).update_all(deleted: true) end flash[:success] = 'Your trash was cleaned!' redirect_to conversations_path end [...] 

и маршрут:

конфиг / routes.rb

 resources :conversations, only: [:index, :show, :destroy] do collection do delete :empty_trash end end 

Пометка разговора как прочитанного

Давайте позволим пользователю отмечать разговоры как прочитанные. Для его реализации нам понадобится еще один метод, маршрут и кнопка:

просмотров / разговоры / _conversation.html.erb

 [...] <div class="btn-group-vertical pull-right"> <% if conversation.is_trashed?(current_user) %> <%= link_to 'Restore', restore_conversation_path(conversation), class: 'btn btn-xs btn-info', method: :post %> <% else %> <%= link_to 'Move to trash', conversation_path(conversation), class: 'btn btn-xs btn-danger', method: :delete, data: {confirm: 'Are you sure?'} %> <% if conversation.is_unread?(current_user) %> <%= link_to 'Mark as read', mark_as_read_conversation_path(conversation), class: 'btn btn-xs btn-info', method: :post %> <% end %> <% end %> </div> [...] 

is_unread? здесь используется метод (пользователь должен быть указан). Есть еще один метод is_read? это делает наоборот.

conversations_controller.rb

 [...] def mark_as_read @conversation.mark_as_read(current_user) flash[:success] = 'The conversation was marked as read.' redirect_to conversations_path end [...] 

Наконец, маршрут:

конфиг / routes.rb

 [...] resources :conversations, only: [:index, :show, :destroy] do member do post :mark_as_read end end [...] 

Brilliant! Вы также можете настроить действие шоу так, чтобы разговор был помечен как прочитанный, когда он открыт… больше домашней работы…

Уведомления по электронной почте

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

конфиг / Инициализаторы / mailboxer.rb

 Mailboxer.setup do |config| #Configures if you application uses or not email sending for Notifications and Messages config.uses_emails = true #Configures the default from for emails sent for Messages and Notifications config.default_from = "[email protected]" #Configures the methods needed by mailboxer config.email_method = :mailboxer_email config.name_method = :name [...] end 

config.email_method и config.name_method сообщают Mailboxer, как получить электронную почту и имя, соответственно. name уже присутствует в нашей модели User , однако mailboxer_email отсутствует. Вы можете попытаться изменить это значение, чтобы оно просто email как этот метод был добавлен Devise, но это может привести к ошибке, поскольку Mailboxer передает ему аргумент, который содержит полученное сообщение. Можно либо переопределить этот метод, либо создать новый. Я буду придерживаться второго варианта:

user.rb

 [...] def mailboxer_email(object) email end [...] 

Уведомления по электронной почте теперь включены (не забудьте настроить ActionMailer, как описано ранее. Также не забывайте, что по умолчанию почта не будет отправляться в процессе разработки. И да, я отключил эту функцию в демонстрационном приложении.)

Вывод

Уф. Это было довольно много для обсуждения, не так ли? Мы рассмотрели основные функции Mailboxer, включая сообщения, различные типы разговоров, управление ими и настройку уведомлений по электронной почте. Мы также интегрировали Devise в приложение и использовали преимущества Gravatar, чтобы все выглядело немного красивее.

Я надеюсь, что этот пост был полезен для вас. Кстати, вас может заинтересовать эта страница википорта Mailboxer и этот пример приложения, представляющего основные функции Mailboxer.

Как всегда, ваши отзывы приветствуются. Если вы хотите, чтобы я затронул конкретную тему, не стесняйтесь спрашивать. Удачного кодирования!

ОБНОВЛЕНИЕ: 2015/03/29

Я получил много отзывов и вопросов от читателей — действительно здорово знать, что мои статьи полезны. Есть вопрос, который задавался несколько раз: «Как добавить кнопку для отправки сообщения конкретному пользователю»? Я считаю, что это довольно распространенная функция, и решил добавить ее в качестве обновления к статье.

Это можно сделать довольно легко. Указанный пользователь должен быть автоматически выбран из выпадающего списка на странице «Начать разговор». Я думаю, что лучший способ предоставить пользователю является использование параметра GET. Так что измените MessagesController следующим образом:

messages_controller.rb

 def new @chosen_recipient = User.find_by(id: params[:to].to_i) if params[:to] end 

Теперь @chosen_recipient содержит либо запись пользователя, либо значение nil .

Вид:

просмотров / сообщений / new.html.erb

 <div class="form-group"> <%= label_tag 'recipients', 'Choose recipients' %> <%= select_tag 'recipients', recipients_options(@chosen_recipient), multiple: true, class: 'form-control chosen-it' %> </div> 

Мы просто передаем @chosen_recipient вспомогательному методу.

messages_helper.rb

 def recipients_options(chosen_recipient = nil) s = '' User.all.each do |user| s << "<option value='#{user.id}' data-img-src='#{gravatar_image_url(user.email, size: 50)}' #{'selected' if user == chosen_recipient}>#{user.name}</option>" end s.html_safe end 

Вот обновленная версия вспомогательного метода receients_options. Просто установите selected атрибут для опции, если пользователь равен выбранному.

По сути, это все. Чтобы продемонстрировать, как это работает, добавьте отдельную страницу со списком пользователей и кнопку «Отправить сообщение» рядом с каждым.

конфиг / routes.rb

 resources :users, only: [:index] 

users_controller.rb

 class UsersController < ApplicationController def index @users = User.order('created_at DESC').paginate(page: params[:page], per_page: 30) end end 

просмотров / пользователей / index.html.erb

 <% page_header "Users" %> <%= will_paginate %> <ul> <% @users.each do |user| %> <li> <strong><%= user.name %></strong> <% unless current_user == user %> <%= link_to 'Send message', new_message_path(to: user.id), class: 'btn btn-default btn-sm' %> <% end %> </li> <% end %> </ul> <%= will_paginate %> 

Там у вас есть это. Поддерживайте обратную связь!