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 messenger
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.
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_email
and 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
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
Remember the @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
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
Mailboxer's
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.
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 _conversation.html.erb
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.
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.coffee
to 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!!
Rails
20 Jun 18
In one of my recent projects, I was working on a scraper that needed to login into a website and download a file which I would then save to use later on. ...
Rails
05 Apr 15
Introduction 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 ...
Ajax
22 Dec 14
With me, is a simple to-do list application where users can create dummy to-do lists and displays them in card-like form just like in Trello. We want to e...
mrblack
09 Apr 15
It's cool. Very clear explanation.
Sean Kelley
26 Apr 15
Hi I am working through this and in: app/views/conversations/show.html.erb the line: should be:
Sean Kelley
26 Apr 15
comments cut off... I changed form_for :message, url: reply_conversation_path(conversation) to form_for :message, url: reply_conversation_path(@conversation)
Sean Kelley
26 Apr 15
Sorry- I missed adding :conversation to the application_controller as a helper.
shalafister
27 Apr 15
Hi!, amazing tutorial thank you very much. I have a problem with js code. I can upload photos and delete photos by clicking the remove button. It actually removes from the database however, keeps the pictures on the screen. They are not vanished. Here is my code, I had to change a bit as I have associated models (boat & picture), If you can help I really appreciate, have been trying to solve for a long time; $(document).ready(function(){ // disable auto discover Dropzone.autoDiscover = false; // grap our upload form by its id $("#picture-dropzone").dropzone({ // restrict image size to a maximum 5MB maxFilesize: 5, // changed the passed param to one accepted by // our rails app paramName: "picture[image]", acceptedFiles: "image/*", //CALISIYOR MU BILMIYORUm // show remove links on each image upload addRemoveLinks: true, // if the upload was successful success: function(file, response){ // find the remove button link of the uploaded file and give it an id // based of the fileID response from the server $(file.previewTemplate).find('.dz-remove').attr('id', response.fileID); $(file.previewTemplate).find('.dz-remove').attr('boat_id', response.boatID); // add the dz-success class (the green tick sign) $(file.previewElement).addClass("dz-success"); }, //when the remove button is clicked removedfile: function(file){ //location.reload(); //removeFile(file); // grap the id of the uploaded file we set earlier var id = $(file.previewTemplate).find('.dz-remove').attr('id'); var boat_id = $(file.previewTemplate).find('.dz-remove').attr('boat_id'); // // make a DELETE ajax request to delete the file $.ajax({ type: 'DELETE', url: '/boats/' + boat_id + '/pictures/' + id, success: function(file){ console.log(data.message); } }); } }); });
shalafister
27 Apr 15
$(document).ready(function(){ // disable auto discover Dropzone.autoDiscover = false; // grap our upload form by its id $("#picture-dropzone").dropzone({ // restrict image size to a maximum 5MB maxFilesize: 5, // changed the passed param to one accepted by // our rails app paramName: "picture[image]", acceptedFiles: "image/*", //CALISIYOR MU BILMIYORUm // show remove links on each image upload addRemoveLinks: true, // if the upload was successful success: function(file, response){ // find the remove button link of the uploaded file and give it an id // based of the fileID response from the server $(file.previewTemplate).find('.dz-remove').attr('id', response.fileID); $(file.previewTemplate).find('.dz-remove').attr('boat_id', response.boatID); // add the dz-success class (the green tick sign) $(file.previewElement).addClass("dz-success"); }, //when the remove button is clicked removedfile: function(file){ //location.reload(); //removeFile(file); // grap the id of the uploaded file we set earlier var id = $(file.previewTemplate).find('.dz-remove').attr('id'); var boat_id = $(file.previewTemplate).find('.dz-remove').attr('boat_id'); // // make a DELETE ajax request to delete the file $.ajax({ type: 'DELETE', url: '/boats/' + boat_id + '/pictures/' + id, success: function(file){ console.log(data.message); } }); } }); });
andrewhttr
28 Jun 18
Can You Update This To latest Software Versions?
shalafister
27 Apr 15
$(document).ready(function(){ // disable auto discover Dropzone.autoDiscover = false; // grap our upload form by its id $("#picture-dropzone").dropzone({ // restrict image size to a maximum 5MB maxFilesize: 5, // changed the passed param to one accepted by // our rails app paramName: "picture[image]", acceptedFiles: "image/*", //CALISIYOR MU BILMIYORUm // show remove links on each image upload addRemoveLinks: true, // if the upload was successful success: function(file, response){ // find the remove button link of the uploaded file and give it an id // based of the fileID response from the server $(file.previewTemplate).find('.dz-remove').attr('id', response.fileID); $(file.previewTemplate).find('.dz-remove').attr('boat_id', response.boatID); // add the dz-success class (the green tick sign) $(file.previewElement).addClass("dz-success"); }, //when the remove button is clicked removedfile: function(file){ //location.reload(); //removeFile(file); // grap the id of the uploaded file we set earlier var id = $(file.previewTemplate).find('.dz-remove').attr('id'); var boat_id = $(file.previewTemplate).find('.dz-remove').attr('boat_id'); // // make a DELETE ajax request to delete the file $.ajax({ type: 'DELETE', url: '/boats/' + boat_id + '/pictures/' + id, success: function(file){ console.log(data.message); } }); } }); });
shalafister
27 Apr 15
//when the remove button is clicked removedfile: function(file){ //location.reload(); //removeFile(file); // grap the id of the uploaded file we set earlier var id = $(file.previewTemplate).find('.dz-remove').attr('id'); var boat_id = $(file.previewTemplate).find('.dz-remove').attr('boat_id'); // // make a DELETE ajax request to delete the file $.ajax({ type: 'DELETE', url: '/boats/' + boat_id + '/pictures/' + id, success: function(file){ console.log(data.message); } }); }
mattshanklin
15 May 15
Great tutorial!!!!! I'm a rails newb and wanted to know how you would customize the conversation/new on each users profile. So if I go to the default user page that there is a "message me" button to message the person directly?
tye
18 Jul 15
Great work. For the "TRASHING AND UNTRASHING MESSAGES" section, the file "show.html.erb" instead of "_conversation.html.erb ". Still great work anyways:)
tye
19 Jul 15
Just in case anyone experience error with Devise migration, like this "NoMethodError: undefined method `merge!' for # 3.4.0' in your Gemfile. Link http://goo.gl/7IR8h7
Manuel
09 Sep 15
How to Write a Migration which can add on location attribute to a column in Mailbox messages : right now conversations have body and subject - i want to add location to it smile thanks in advanced!
ajazshaik
29 Jul 16
Hi Joseph can you make a tutorial to send sms to users
Michael Devenport
17 Aug 15
Hi Joseph, i'm still new to app-development and could do with a point in the right direction. I have implemented user avatars using paperclip which is working, i can call <%= image_tag(current_user.avatar.url(:thumb)) %> within the layouts nav partial, but i would like to replace the 'placeholders/images' with according user avatars. How do i render user avatar images in _conversations.html.erb and _messages.html.erb ? Thanks.
Joseph Ndungu mod
17 Aug 15
Hello Michael, assuming your paperclip attached file is called "avatar", in _conversations.html.erb change line 4 to something like `<%= image_tag conversation.originator.avatar.url(:thumb) %>`..for _messages.html.erb change line 6 to something like `<%= message.sender.avatar.url(:thumb) %>`. That should show the users' respective avatars.
Michael Devenport
17 Aug 15
Hi Joseph, Thanks for your response but i'm confused, your answer does not contain any code? I might be new to RoR but even i know ' .. ' will not resolve my issue .. Could you please send the code to my email at croxleyway@gmail.com or repost here or tweet me maybe. Thanks very much ;-)
Michael Devenport
17 Aug 15
Hi Joseph, Thanks for your response but i'm confused, your answer does not contain any code? I might be new to RoR but even i know ' .. ' will not resolve my issue .. Could you please send the code to my email at croxleyway@gmail.com or repost here or tweet me maybe. Thanks very much ;-)
Michael Devenport
17 Aug 15
Got it ! THANK YOU SO MUCH, i love your work my friend these tutorials are awesome . Checkout your gmail/hangouts real time messaging app i deployed with Avatars at http://www.efilecabi.net/ Thank you. Michael
Aaronman2
11 Sep 15
Hi Michael, can you please tell me how you added the avatar functionality. I am bit new to all this and trying to learn from tutorial... I have an app and i want to add avatars system but i want to give users some selected avatars just like stackoverflow
Michael Devenport
02 Dec 15
AARONMAN2 - If you want to add Avatars to a Rails app there are a number of steps involved - to many steps to post here.. Email direct at croxleyway@gmail.com for help with your issue, or i recommend you take a look into https://www.youtube.com/use... he is awesome and has about 20 or more free rails tutorials and web design tutorials. Sorry for the very late response, i have only just noticed your comment. Happy to help. Michael.
Maqnai2234
02 Sep 15
Thank, you, the most tutorial!
Manuel
07 Sep 15
The email notifications don't work? Any source where i can readup on it?
Joseph Ndungu mod
07 Sep 15
Check mailboxer documentation https://github.com/mailboxe.... In your initializer you should set "config.uses_emails" to true
Manuel
07 Sep 15
Am I going to have to get into the ActionMailer - Don't know what to do next
Manuel
07 Sep 15
Hey Jo, Did that > then i pushed it up to heroku: https://enigmatic-earth-306... Still get an error: https://enigmatic-earth-306...
Manuel
08 Sep 15
Set up mail with Mandrill - Now I'm looking to migrate new message fields - I'm thinking rails g migration add_fields_to_message title:string other:text other:text
Aaronman2
12 Sep 15
Hey Joseph, the tutorial was amazing thanks for that but i have a question, i wanna ask how you added search system to search Recipients because right now i followed your tutorial and got the app working but instead of the search it's giving me select menu with the name of users
naiyyar
03 Oct 15
Hi Joseph, thank you very much, these tutorials are awesome. i have a question, How can i add file attachment option with paperclip? i tried adding below code in initializers/mailboxer.rb but didn't get success. Notification.class_eval do has_attached_file :attachment end Thanks
hktangudu
14 Oct 15
hi, i am getting an below error Showing C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/mailboxer-0.13.0/app/views/mailboxer/message_mailer/new_message_email.html.erb where line #17 raised: Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true can you help me ?
leongg
30 Oct 15
Joseph awesome !! got a question, is there a way to mix you gmail like chat app with this tutorial? Thanks!
Joseph William Hays
04 Dec 15
An amazing tutorial! However, I keep running into a problem with the @receipts. I'm getting an error from Action Controller: ` NoMethodError in Mailbox#inbox: undefined method `each' for nil:NilClass`. What's going on here?
Lori Tiernan
18 May 16
I'm having the same issue. Did you resolve?
john-lanticse
19 Jul 17
the trashing and untrashing should be in show.html.erb not in _conversation.html.erb It solved my problem for future reference :)
Himal
08 Dec 15
Hey there! This tutorial was a huge help to me, I really appreciate the time you put into this tutorial. I unfornuatly was unable to get the chosen part working. I followed your steps but the form did not change at all. Do you know why this would be? Thanks!
Joseph Ndungu mod
08 Dec 15
Hello Himal, thank you so much and its great to hear the tutorial was of help. For the chosen part, please check your browser console for any errors and check you have the correct class in your select dropdown.
Himal
08 Dec 15
Hey I gave that a shot and there were not errors. I believe I have the correct class whitch I have "chosen-select form-control" . Could it be a potental issue that I am using Rails 4? I am really at a loss here with what the issue is. I appreciate your help.
RakeshTejra
15 Dec 15
HI Is this possible to create more than two conversations between two same users? Because I tried this and it said: "Validation failed: Notification base This message must be added to existing conversation (#)" Thanks!
Joseph Ndungu mod
02 Apr 16
The partial should be in your conversations folder. Please check the example app used for this tutorial to spot where you went wrong https://github.com/Joseph-N...
CharlesEricLD
19 Oct 16
Hi Joseph, I followed the tutorial step by step and it worked perfectly, but now, I don't know why ! I checked on my Rails Admin all the message, conversation appears but they are still not displaying in the inbox view... Could you help me please ?
Baptiste Becmeur
02 Apr 16
Hey Joseph, I don't understand your example can't work. There is a template missing error.
charleswdickstein
30 Jan 16
Hello, Great tutorial. I have an issue when a user clicks Message in a Users#index: The user still has to click "Compose" on the next page then has to find the user he wants to message. I want to be able to click "Message" in the user index and start a new conversation with that user immediately after. I have tried variations of code like but to no avail. Thanks!
charleswdickstein
31 Jan 16
For example:
charleswdickstein
31 Jan 16
Baptiste Becmeur
02 Apr 16
Hey Joseph, Thank you so much for this Tuto! I have a question I follow step by step what you suggest and I have a little error. The mailbox/sent view doesn't appears. Here is the message error: Missing partial mailbox/_messages, application/_messages with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder]}. Maybe I miss a step. Your help is appreciated. Thank you.
Joseph Ndungu mod
02 Apr 16
Make sure you have the messages partial (app/views/conversations/_messages.html.erb) in your conversations view folder. All the best and thanks!
Baptiste Becmeur
02 Apr 16
Hey, I do this but I doesn't work. Maybe I call it bad in mailbox/_messages?
Qasem Hajizadeh
26 Jun 16
Hi Joseph You can make a tutorial for your commenting system ? Thanks
CharlesEricLD
12 Sep 16
Hi Joseph, Thanks for that tutorial !! Do you know how it would be possible to hide the recipient field and add a button on a different show page like "Send a message @User1" ?
stephane cedroni
08 Feb 17
naveen1336
16 Mar 17
how to add attachment to the mails.
john-lanticse
19 Jul 17
The following is the error i encountered and how i solved it. Error: Please provide the :host parameter, set default_url_options[:host], or set :only_path Solution: Adding this in the routes default_url_options :host => "example.com" Error: Here is the message error: Missing partial mailbox/_messages, application/_messages or ` NoMethodError in Mailbox#inbox: undefined method `each' for nil:NilClass` Solution: the trashing and untrashing views should be in the show.html.erb not in the _conversation.html.erb
RailsCod3rFuture
18 Aug 17
Can you post a version this tutorial with multiple model type support. User Admin or Buyer Seller?
RailsCod3rFuture
19 Aug 17
Can you post a version this tutorial with multiple model type support. User Admin or Buyer Seller?
mikehamlick
20 Nov 17
Question. I implemented this and it works. Now I'm looking to have a fixed Recipient rather than a selected recipient. So instead of I want it to have the recipient pre-designated. I tried instead as I want to be able to message a job applicant. But it's not working. Any ideas?
mikehamlick
20 Nov 17
Did you figure this out? I'm trying to implement something similar. Where the recipient is pre-designated rather than having the option of selecting the user.