Private Inbox System in Rails with Mailboxer
05 Apr 15
Here I show how to implement a private messaging system with mailboxer gem.
It's been quite a while since my last tutorial and since then I've recieved alot of requests by email to implement a private messaging system in Rails. In this tutorial I will guide you on how to implement such a system using the
Mailboxer gem. The Gem's documentation is not that sufficient so i'll try to do be as detailed as possible.
You can take a look at the
Demo App on heroku, the source code for this tutorial can also be found on my Github Repo. Feel free to clone it!
We want to create a simple application that lets logged in users send and receive messages privately between themselves. Our application should keep a list of messages organized into folders: sentbox, inbox and trash and also ability to send notifications via mail.
Lets start out by creating a new rails app, lets name it
rails new messenger
I'll be using bootstrap css under the hood for some quick styling. Next up, we will need to set up and authentication mechanism, we'll use
Devise for that. Let's also throw in our Mailboxer gem at this point and leave it to simmer for a while while we work on authentication.
We then install our gems using the bundle command
$ bundle install
We now need to run Devise's generator to install an initializer that will describe all options available to configure Devise.
$ rails generate devise:install
Completing the additional steps as described on the post-install messages that appear after running the above generator, lets create a controller and name it
welcome with one action.
$ rails g controller welcome index
Lets make the root path of our application point to the index action of our newly created welcome controller
We then need to tweak the application.html layout file a bit to add the flash messages
You'll notice the use of the
flash_class function that takes the key as an argument, that's actually a helper method I define in the application_helper.rb in the helpers folder. Used to return the appropriate bootstrap classes based on the flash key.
I've also created a
_nav partial that holds our applications navigation in the layouts folder.
It's also a good idea to copy over the Devise's views to our project so that we can tweak them
$ rails generate devise:views
Next up, lets create a User model configured with Devise modules, Devise has an awesome generator for this:
$ rails generate devise User
$ rake db:migrate
The generator also configures your config/routes.rb file to point to the Devise controller. You can check the User model for any additional configuration options you might want to add but for our case we will leave it at its defaults. At this point we can update the login and sign up links in our
_nav partial to point to the appropriate routes.
For our application, we want users to provide their names during registration, lets create a migration to add a
name attribute to our users.
$ rails g migration AddNameToUsers name:string
$ rake db:migrate
We then add the name input field to Devise's views
Since we are running this app on Rails 4, we will need to whitelist the name parameter. In our application controller file:
We can now at this juncture tackle the elephant in the room. We already added the Mailboxer gem already in our Gemfile and we can proceed to install it and update our database.
$ rails g mailboxer:install
$ rake db:migrate
The generator creates an initializer file
mailboxer.rb which you should use this to edit the default mailboxer settings. The options are quite straight forward, tweak them to your liking. We will then change the default
name_method since we already have the name attribute in our user model to prevent conflict.
Preparing our model
For us to equip our User model with Mailboxer functionalities we have to add
acts_as_messageable. This will enable us send user-user messages. We also need to add an identity defined by a name and an email in our User model as required by Mailboxer.
Our model must therefore have this specific methods. These methods are:
mailboxer_name as stated in our mailboxer initializer file. You should make sure to change this when you edit their names in the initializer.
The way I would like to configure this is having two controllers
- MailboxController - This will hold our top-level folders (inbox, sentbox and trash) and will be responsible for displaying the messages in each of this folders.
- ConversationsController - This will handle various actions including creating conversations, replies and deletion of messages etc.
Let's start by creating the mailbox controller
$ rails g controller mailbox
We will then add some custom routes for inbox, sentbox and trash in our routes.rb file
Lets define this methods in our mailbox controller
We will use the
@active variable to highlight the currently selected folder in our view. We can now define the
mailbox method as a helper method in our application_controller.rb file.
Let's now create a view file for each of the actions in the mailbox controller. Each of this will share the same partial to navigate between the mailbox folders
Lets create the
folder_view partial, in our mailbox folder, create a new file name it
_folder_view.html.erb and paste the following contents.
Inside here, we also make use of another partial called
folders. Lets also, in the same mailbox folder create a new file _folders.html.erb and have the following contents
@active instance variable we instantiated in our mailbox controller? lets make a helper method that makes use of that variable to highlight the current page. In our application_helper.rb file add the following method.
For our inbox, it would be a good idea to display the number of unread messages. We will define a helper method in our
mailbox_helper.rb file in the helpers directory.
We can now add a link in our navigation bar to link to our inbox page
Clicking the inbox link should take you to our up and running inbox page with navigation already in place for the inbox, sent and trash folders.
With our mailbox ready to show messages from our inbox, sent and trash folders, its now time we created functionality to create and send new messages to other users. Lets go ahead and create our second controller (conversations)
$ rails g controller conversations
Let's not forget to add a resources route for our conversations which will also hold other 3 member routes to handle reply, trash and untrashing of messages.
With the conversations routes in place, lets edit our compose button link in our folder_view partial to point to the new action of our conversations controller.
We now need to create the new method in our conversations controller and its corresponding view file
Notice we are still using our
folder_view partial which is in our mailbox folder. This partial will be responsible for displaying different contents based on the current view. To start with, we are passing in a locale called
is_conversation. When this is set to true, we will render the form to create new conversations and messages, else we will show contents for each mailbox folder.
In our views folder, conversations folder, create a form partial called
_form.html.erb, that will hold the form to create new conversations.
We then need to update our
folder_view partial to render our conversation form if the is_conversation variable is set to true.
Let's not forget to update our mailboxes passing the
is_conversation locale as false. Update our mailbox view files (inbox.html.erb,sent.html.erb,trash.html.erb) to read
inbox.html.erb, sent.html.erb, trash.html.erb (app/views/mailbox/)
With this, clicking on the
Compose button on either page should take you to the new view of our conversations controller and you should have a beautiful form ready to send messages
With our form up and running, we need to implement the
create action in our conversations controller to save the messages to the database. We call Mailboxer's
send_message method on the current user and that takes in an array of recipients, the message body and message subject after which we redirect to the show action of our conversations controller.
As you can see in the show action, we query all messages in the current conversation for the current user and store that in the
@reciepts variable. Also, the
conversation is actually a helper method we need to define in our application controller to help us get the current conversation.
Creating the show view of the show action:
Notice we call another partial
messages in our new view. Create a new partial _messages in the conversations folder.
Creating a new conversation should successfully redirect you to that specific conversation and you should have your beautiful conversation show page like the one below
Now that we can successfully send a message, we need to mechanism to reply correct? we will need to add a reply form on this page and a subsequent reply action in our conversations controller.
Now lets add the reply action to our controller
reply_to_conversation method makes replying to conversations a breeze. It takes in a conversation and the message body and optionally a subject as arguments. If you want users to change the subject during reply, then remember to add the subject text field in the reply form and also in the reply_to_conversation method above as the third argument.
Displaying messages in our mailbox folders
Our mailbox controller views are lonely and we need to show contents in each of them from which users can click on a snippet and view the full message. In this folders, we want to show a snippet of the last message in each unique conversation.
Lets create a new partial
conversation inside our conversations folder name it
Updating our inbox, sent and trash views
In all of our three views, we pass in messages as a locale to our folder_view partial which represents messages from either our inbox, sentbox or trash. Finally lets update our
folder_view partial to use the messages locale and consequently render the conversation partial we just created.
You should now be able to see your messages, if any, when you click the inbox and sent links in any view. One last part is remaining, as you can guess, that is ability to delete messages and send them to the trash and also ability to untrash messages which we might have deleted by mistake.
Trashing and Untrashing Messages
We will now need to add a "Move to trash" button in each conversation that is not yet deleted. For those already deleted, we need to show an "Untrash" button.
We now add the corresponding trash and untrash methods in our controller
As you can see Mailboxer provides handful methods
move_to_trash to send messages to the current users's trash box and
untrash to move back the message back to the users inbox.
As you many have noted, the current method of selecting recipients is not very efficient especially when the number of users increase, finding a user can become way too tedious. We can improve on this by use of a jQuery plugin called
Chosen which makes it dead simple to select from a large list and is more user-friendly. Fortunately for us, there is a chosen-rails gem that makes integrating this plugin into Rails app very easy.
To Install the gem in our rails app, add chosen-rails to your Gemfile and run bundle install
We then need to add Chosen to application.js and application.css.scss files:
We then add the
chosen-select class to our select dropdown in our conversation's form select field
Lastly, lets equip our multiple-select dropdown with some Chosen magic. As we will be using plain jquery, rename
conversations.js and paste in the following code
Reload the page with your form and you should see jQuery Chosen in action.
That wraps it up for this tutorial and I hope I did a good job of trying to explain the nitty-gritty of implementing a private messaging system using the Mailboxer Gem. Its my hope that this post was useful and as always let me know what you think in the comments section below. Happy Coding!!