Mailboxer 是一個 Rails gem,為 social_stream 框架用來建立社群網路的一部分。它是具備一些通用方法的訊息系統,允許任何 model 擔任傳遞訊息的角色。
使用 Mailboxer,你可以與一或多個收件人建立對話(訊息會被歸納到寄件匣、收件匣及垃圾桶)並透過電子郵件發送通知。它甚至可以在不同 model 間發送訊息和附件!唯一的缺點是缺乏文件,所以我希望這邊文章是有幫助的。
我們要討論的範例程式為:
- 使用 Devise 做基本身份驗證
- 使用 Gravatar 讓使用者管理頭像
- 整合 Mailboxer
- 使用圖形介面來建立/回覆對話(使用 Bootstrap 樣式及 Chosen jQuery 插件)
- 顯示資料夾,並允許在它們之間輕鬆切換
- 允許將對話標示為已讀、放到垃圾桶或還原,也可以清理垃圾桶。
- 設定電子郵件通知
此範例將使用 Rails 4,但幾乎相同的解決方案也可以實作於 Rails 3.2(Mailboxer 不再支援 3.1 版)。
原始碼可以在 Github 找到。
前置準備
假設我們要建立內部的私人訊息系統提供同事討論不同主旨。這個系統應該允許使用者與無數個收件人建立對話、提供通知系統並允許刪除舊的對話。
建立名為 Synergy 不含預設測試套件的 Rails 應用程式:
$ rails new Synergy -T
在你的 Gemfile 加入以下的 Gem(我會使用 Bootstrap,但你可以使用任何其它的 CSS 框架、自行設計或者跳過美化網站的步驟):
[...]
gem 'bootstrap-sass'
gem 'kaminari'
[...]
執行
$ bundle install
$ rails generate kaminari:views bootstrap3
然後加入 Bootstrap 檔案:
@import "bootstrap-sprockets";
@import "bootstrap";
//= require bootstrap-sprockets
接著調整 layout:
[...]
<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>
[...]
讓我們新增 helper 來方便呈現頁面標題:
[...]
def page_header(text)
content_for(:page_header) { text.to_s }
end
[...]
身份驗證
實作訊息功能之前,我們需要一個 model 來傳遞訊息。建立 User model:
$ rails g model User name:string
$ rake db:migrate
你可以使用任何類型的身份驗證,但我喜歡 Devise。Devise 的基本設定非常簡單,並有大量文件協助您進一步自訂設定。
加入新的 gem:
[...]
gem 'devise'
[...]
並安裝它:
$ bundle install
現在,我們可以利用 Devise 的產生器來幫我們做一些工作:
$ rails generate devise:install
請務必閱讀安裝後的訊息來完成一些額外的步驟。具體來說,你需要為 development 和 production 調整 config.action_mailer.default_url_options
設定,因為它會被用來發送郵件給使用者(例如,幫助他們恢復遺忘的密碼)。
請注意,電子郵件在 development 時不會發送,除非你在 config/environments/development.rb
設定 config.action_mailer.perform_deliveries = true
。
這裡有關於如何設定 ActionMailer 的一些範例。
當你準備好後,執行以下指令使用 Devise 建立 User model:
$ rails generate devise User
$ rake db:migrate
你可能想要在套用變更前檢查指令產生的遷移檔,並加入更多欄位到資料表(啟用 Confirmable 或 Lockable 模組)。你還需要調整 model 內相應的設定。
最後,執行以下指令來複製 Devise 的 views 到你的專案,以便稍作修改:
$ rails generate devise:views
要能夠讓使用者變更他們的名字,所以新增一個欄位到註冊表單:
[...]
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
[...]
同樣的將此欄位加入 app/views/devise/registrations/edit.html.erb
(或者將它重構為 partial),讓使用者可以在註冊時提供名字,並在稍後編輯它。
由於 Rails 4 引進了新的保護機制 Strong Parameters,需要設定允許 :name
參數可以傳遞:
[...]
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
屬性。如果你忘了這樣做,使用者將無法設定自己的名字。
此時,你也可以修改 view 的樣式。我不會涵蓋這一步驟,因為它不是太難且高度依賴於你的設定(無論你是不是使用 Bootstrap)。如果你決定使用 Bootstrap,Devise 所產生的訊息將不會有樣式。為了解決這個問題,使用 SASS @extend
方法,如下:
[...]
.alert-notice {
@extend .alert-success;
}
.alert-alert {
@extend .alert-warning;
}
整合 Mailboxer
太好了,我們準備進行主要的任務-整合及設定 Mailboxer。
首先,加入新的 gem:
[...]
gem 'mailboxer'
[...]
並安裝它:
$ bundle install
產生和套用所有必要的遷移並建立初始化檔案:
$ rails generate mailboxer:install
$ rake db:migrate
查看 config/initializers/mailboxer.rb
檔案看你能修改哪些選項。現在,先維持檔案內的設定,稍後我們會設定寄送電子郵件通知。
需要稍作調整 model 來配置 Mailboxer 的功能:
[...]
acts_as_messageable
[...]
顯示對話
如指南所建議,為 Mailboxer 建立圖形介面的最佳方法是建立兩個 controller:一個用於訊息,一個用於對話。獨立的訊息會被歸類為對話。稍後,你會看到對話可以是不同類型。
首先建立對話 controller:
$ rails generate controller conversations
class ConversationsController < ApplicationController
before_action :authenticate_user!
before_action :get_mailbox
def index
@conversations = @mailbox.inbox.page(params[:page])
end
private
def get_mailbox
@mailbox ||= current_user.mailbox
end
end
每個使用者都有自己的信箱,反過來說,又分為收件匣、寄件匣及垃圾桶。目前,我們先關注在收件匣就好。
authenticate_user!
是 Devise 的一部分。我們只希望通過驗證的使用者存取應用程式,因此它設定為 before_action
。如果使用者沒有通過驗證,他將被導向到登入頁面。
如你所見,我也使用了 kaminari
所提供的 page
方法。
新增路由(其它 controller 方法很快就會加入):
[...]
resources :conversations, only: [:index, :show, :destroy]
[...]
並修改 view:
<% page_header "Your Conversations" %>
<ul class="list-group">
<%= render partial: 'conversations/conversation', collection: @conversations %>
</ul>
<%= paginate @conversations %>
page_header
是我們先前建立的 helper 方法。paginate
會顯示分頁控制項(只有超過一頁時會顯示)。
我們必須指定 partial
參數,因為 @conversations
是 Mailboxer::Conversation::ActiveRecord_Relation
的一個實例,因此 Rails 預設會在 mailboxer/conversations
目錄尋找 _conversation
。
新增 partial:
<li class="list-group-item clearfix">
<%= link_to conversation.subject, conversation_path(conversation) %>
</li>
每個對話都有一個主旨和一些將被顯示於頁面上的訊息。
新增選單到 layout:
[...]
<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>
[...]
user_signed_in?
方法以及大部分的路由都是由 Devise 提供。
接下來是 show 動作:
class ConversationsController < ApplicationController
before_action :authenticate_user!
before_action :get_mailbox
before_action :get_conversation, except: [:index]
def index
@conversations = @mailbox.inbox.page(params[:page])
end
def show
end
private
def get_mailbox
@mailbox ||= current_user.mailbox
end
def get_conversation
@conversation ||= @mailbox.conversations.find(params[:id])
end
end
你可能知道,當查詢不到資料時會拋出例外錯誤。這就是我們想要的,但是應該要處理例外錯誤。為了簡單起見,我們使用 rescue_from
方法:
[...]
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
。
接著,編輯 view:
<% 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("%F %T") %>
<%= message.body %>
<% end %>
</div>
</div>
</div>
我們呈現了每個訊息的寄件者的名字、建立時間及訊息內容。讓我們來修改一下 .messages
容器的樣式,讓它不要變得太高:
[...]
.messages {
max-height: 400px;
overflow-y: auto;
margin-bottom: 1em;
margin-top: 1em;
}
不錯,一些基本的 view 都已經存在了。然而,我們還缺乏了一些重要的東西:
- 使用者應該知道在和誰對話
- 使用者需要可以建立新的對話
- 使用者需要可以回覆對話
- 寄件匣和垃圾桶應該顯示在對話頁面
- 使用者應該能將對話標示為已讀
顯示使用者頭像
雖然這和 Mailboxer 無關,我認為顯示頭像會讓我們的應用程式看起來更漂亮。然而,允許使用者直接上傳頭像到應用程式有點小題大作,讓我們使用 Gravatar 並透過 gravatar_image_tag 整合到 Rails。
加入新的 gem:
[...]
gem 'gravatar_image_tag'
[...]
並執行
$ bundle install
同樣的,新增 helper 來方便呈現頭像:
[...]
def gravatar_for(user, size = 30, title = user.name)
image_tag gravatar_image_url(user.email, size: size), title: title, class: 'img-rounded'
end
[...]
建立單獨的 partial 來呈現對話內收件人的頭像(除了目前的使用者):
<% conversation.participants.each do |participant| %>
<% unless participant == current_user %>
<%= gravatar_for participant %>
<% end %>
<% end %>
編輯以下的 view:
<% 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("%F %T") %>
</h6>
<%= message.body %>
</div>
</div>
<% end %>
</div>
</div>
</div>
<li class="list-group-item clearfix">
<%= link_to conversation.subject, conversation_path(conversation) %>
<p><%= render 'conversations/participants', conversation: conversation %></p>
</li>
顯示對話內最後一則訊息及它的建立時間:
<li class="list-group-item clearfix">
<%= link_to conversation.subject, conversation_path(conversation) %>
<p><%= render 'conversations/participants', conversation: conversation %></p>
<p>
<%= conversation.last_message.body %>
<small>(<span class="text-muted"><%= conversation.last_message.created_at.strftime("%F %T") %></span>)</small>
</p>
</li>
我們已經完成頭像了。是時候讓使用者建立新的對話。
建立對話
建立對話實際上意味著建立帶有主旨的新訊息(雖然這是可選的)。這表示需要新的 controller:
$ rails generate controller messages
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’] 中)並利用 Mailboxer 的 send_message
方法,傳入收件人、訊息和主旨。稍後,我們將啟用電子郵件通知,以便讓使用者知道收到新訊息了。
現在來編輯 view:
<% 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
是 helper 方法,我們需要建立它:
module MessagesHelper
def recipients_options
options_for_select(User.all.map { |user| [user.name, user.id] })
end
end
別忘了設定路由:
[...]
resources :messages, only: [:new, :create]
[...]
接著在 conversations#index
頁面顯示「Start conversation」連結:
<% page_header "Your Conversations" %>
<p><%= link_to 'Start conversation', new_message_path, class: 'btn btn-lg btn-primary' %></p>
[...]
技術上來說,一切都已經就緒可以發佈你的第一則訊息。你可以傳遞給自己或是註冊另一個帳號來模擬有兩個使用者的情況。
然而,選擇收件人不是很方便。目前,已經呈現了基本的選擇欄位,但如果有很多使用者時,要在列表中找到某個人是很麻煩的。我們可以使用 Chosen 來強化這個欄位,它是讓下拉選單更加人性化的一個 jQuery 插件。有個 chosen-rails gem 可以輕鬆的將此插件整合到 Rails 應用程式。
將這個 gem 加到 Gemfile:
[...]
gem 'chosen-rails'
[...]
我也指定了 sass-rails
與 coffee-rails
的版本,因為有與 application.scss 檔案相關的 bug:
[...]
gem 'sass-rails', '~> 4.0.5'
gem 'coffee-rails', '~> 4.1.0'
gem 'jquery-turbolinks'
[...]
同時也使用了 jquery-turbolinks gem,當使用 Turbolinks 時可恢復預設的 jQuery page load 事件。
別忘了執行
$ bundle install
然後將 Chosen 加到 application.js
和 application.scss
:
[...]
//= require jquery.turbolinks
//= require chosen-jquery
[...]
[...]
@import "chosen";
[...]
接著將 .chosen-it
類別加到我們的 select 標籤:
[...]
<div class="form-group">
<%= label_tag 'recipients', 'Choose recipients' %>
<%= select_tag 'recipients', recipients_options, multiple: true, class: 'form-control chosen-it' %>
</div>
[...]
並將所有此類別的元素都裝上 Chosen 的功能:
$ ->
$('.chosen-it').chosen()
現在重載伺服器,到 conversations/new 頁面,會發現嶄新的 select 標籤。這用起來更加方便,不是嗎?
我們可以更進一步的在 select 標籤內使用者名字前顯示頭像。有個 Chosen 的擴充插件 Image-Select。只要將 ImageSelect.jquery.js 和 ImageSelect.css 放到你的專案並分別在 application.js 和 application.scss 引入。然後,稍微修改 helper 方法:
module MessagesHelper
def recipients_options
options_for_select(User.all.map { |user| [user.name, user.id, { 'data-img-src' => gravatar_image_url(user.email, size: 50) }] })
end
end
接著再重載伺服器並確認成果。非常棒!
回覆對話
現在,使用者可以建立對話,但是沒有辦法回覆!為了解決這個問題,我們需要另一個表單及 controller 方法,以及新的路由:
[...]
<%= 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 %>
註:你也可以加入另一個文字欄位讓使用者新增主旨。
[...]
def reply
current_user.reply_to_conversation(@conversation, params[:body])
flash[:success] = 'Reply sent'
redirect_to conversation_path(@conversation)
end
[...]
Mailboxer 的 reply_to_conversation
方法讓回覆對話變得很輕鬆。它接受對話的回覆訊息、主旨(可選)及些許其他的參數。需要注意的是,如果對話被移動到垃圾桶(我們將在稍後處理),預設會被復原。可以去看看原始碼取得更多資訊。
[...]
resources :conversations, only: [:index, :show, :destroy] do
member do
post :reply
end
end
[...]
很好,基本的聊天系統已可啟動並執行!
實作寄件匣及垃圾桶
目前,我們只有顯示使用者的收件匣。然而,顯示寄件匣與垃圾桶是個好主意。
也許要決定該顯示哪個資料夾最簡單的方式是使用 GET 參數,所以讓我們來調整 controller:
[...]
before_action :get_box, only: [:inbox]
def index
if @box.eql? "inbox"
@conversations = @mailbox.inbox
elsif @box.eql? "sent"
@conversations = @mailbox.sentbox
else
@conversations = @mailbox.trash
end
@conversations = @conversations.page(params[:page])
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
用來取得所需的資料夾。
在 view 的地方,如果你使用的是 Bootstrap,我建議使用垂直導覽列來呈現資料夾。此外,目前所在的資料夾應該被高亮。建立 helper 方法來處理這個:
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
方法的 hash 格式選項。然後檢查 opts
是否已有類別的屬性。沒有的話就設為空字串,並在目前的資料夾的類別附加 active
類別。
修改 view:
<% page_header "Your Conversations" %>
<p><%= link_to 'Start conversation', new_message_path, class: 'btn btn-lg btn-primary' %></p>
<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>
<%= paginate @conversations %>
</div>
</div>
下一步是在每個尚未丟棄到垃圾桶的對話加入「Move to trash」按鈕。對於丟棄在垃圾桶的對話,應該顯示「Restore」按鈕。
<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 %>
</div>
<p><%= render 'conversations/participants', conversation: conversation %></p>
<p>
<%= conversation.last_message.body %>
<small>(<span class="text-muted"><%= conversation.last_message.created_at.strftime("%F %T") %></span>)</small>
</p>
</li>
增加相應的方法:
[...]
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 提供的兩個方法,從命名就能知道用途。
修改路由:
[...]
resources :conversations, only: [:index, :show, :destroy] do
member do
post :reply
post :restore
end
end
[...]
那麼「Empty trash」按鈕呢?很簡單:
[...]
<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>
<%= paginate @conversations %>
</div>
[...]
以及相應的方法:
[...]
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
[...]
並增加路由:
[...]
resources :conversations, only: [:index, :show, :destroy] do
member do
post :reply
post :restore
end
collection do
delete :empty_trash
end
end
[...]
將對話標示為已讀
讓我們允許使用者將對話標示為已讀。為了實作它,我們需要另外的方法、路由及按鈕:
[...]
<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?
。
[...]
def mark_as_read
@conversation.mark_as_read(current_user)
flash[:success] = 'The conversation was marked as read.'
redirect_to conversations_path
end
[...]
最後,修改路由:
[...]
resources :conversations, only: [:index, :show, :destroy] do
member do
post :reply
post :restore
post :mark_as_read
end
collection do
delete :empty_trash
end
end
[...]
大功告成!
註:你還可以優化 show 動作,讓對話被開啟時自動標示為已讀!
電子郵件通知
記住,Mailboxer 可以在使用者收到訊息時寄送電子郵件通知。此功能在 initializer 中啟用:
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 = "no-reply@mailboxer.com"
#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 model,但是沒有 mailboxer_email
。你可以嘗試改變這個值為 Devise 所提供的 email
方法,但是這會導致一個錯誤,因為 Mailboxer 會傳遞參數給它,包含收到的訊息。所以有兩個選擇,一個是重新定義此方法,或是建立一個新的。我會選擇第二個選項:
[...]
def mailboxer_email(object)
email
end
[...]
電子郵件通知已經啟用了(請確定有依照先前的指示設定 ActionMailer。另外,不要忘記,電子郵件在 development 時預設是不會發送的。)
補充:如何新增按鈕來寄送訊息給指定使用者
這個功能可以很容易地完成!指定的使用者應該從「Start conversation」頁面上的下拉選單自動選取。我認為,最好的方式就是使用 GET 參數帶入指定的使用者。修改 MessagesController
如下:
[...]
def new
@chosen_recipient = User.find_by(id: params[:to].to_i) if params[:to]
end
[...]
現在,@chosen_recipient
會有使用者的資料或是 nil
。
接著修改 view:
[...]
<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
給 helper 方法。
[...]
def recipients_options(chosen_recipient = nil)
options_for_select(User.all.map { |user| [user.name, user.id, { 'data-img-src' => gravatar_image_url(user.email, size: 50) }] }, chosen_recipient.nil? ? nil : chosen_recipient.id)
end
[...]
這是更新後的 recipients_options
helper 方法。將預設選取的選項帶入 options_for_select
的第二個參數即可!
基本上,這樣就完成了!為了示範如何運作,新增一個使用者清單頁面並於每個使用者後方加上「Send message」按鈕。
[...]
resources :users, only: [:index]
[...]
建立使用者 controller:
$ rails generate controller users
class UsersController < ApplicationController
def index
@users = User.order('created_at DESC').page(params[:page])
end
end
<% page_header "Users" %>
<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>
<%= paginate @users %>
調整 layout 加入使用者清單頁面連結:
[...]
<li><%= link_to 'Users', users_path %></li>
[...]
到此,這個功能就完成囉!
結論
呼!討論了相當多,對吧?我們探討了 Mailboxer 的基本功能,包含了訊息、不同類型的對話、管理對話以及設定電子郵件通知。我們也整合了 Devise 到此應用程式並利用 Gravatar 讓頁面看起來更美觀。
希望本文對你有幫助。順帶一提,你可能會對 Mailboxer 維基上的這個頁面以及介紹 Mailboxer 基本功能的應用程式範例有興趣。