Messaging with Rails and Mailboxer

Originally published at: http://www.sitepoint.com/messaging-rails-mailboxer/

Mailboxer is a Rails gem that is a part of the social_stream framework for building social networks. It is a generic messaging system that allows any model to act “messageable”, equipping it with some versatile methods. With Mailboxer, you can create conversations with one or more recipients (messages are organized into folders – sentbox, inbox, trash) and send notifications via email. It is even possible to send messages between different models and add attachments! The only drawback is the lack of documentation, so I hope this post will be useful.


Continue reading this article on SitePoint

Thank you for creating a real solution on top of the mailboxer gem. As you stated earlier, the documentation is so concise that you can't utilize it's full capabilities without understanding every aspect of it. What I wanted to ask you is how exactly would I allow two different user models to communicate with each other. What you've done so far works great for the normal user interaction, but I created another model with devise biz_users. Must I redo all of the code with a different controller just to allow such a thing or is their an easier way to get both models speaking.

I am glad that this post was useful smile

Well, communicating between the two models should not be much harded that with only one (https://github.com/mailboxer/mailboxer#preparing-your-models). What will cetrainly change is this line https://github.com/bodrovis/SitePoint-Mailboxer/blob/master/app/controllers/messages_controller.rb#L8 because you'll need to add support for another model. But all in all most of the codebase will stay intact. For example, this helper https://github.com/bodrovis/SitePoint-Mailboxer/blob/master/app/helpers/messages_helper.rb#L4 should be changed as well (I am not sure if you want to present both users and biz_users in that list).

So there is no need to create additional controllers - those two are perfectly fit for working with messages and conversations.

Can you give me a quick solution that would work for two user models with the messages_controller and messages_helper. I don't want to end up breaking the one you already have in place.

Well, I've pinpointed lines that need replacing - so just go ahead and modify them so that two user models are being used. Like User.where(id: params['recipients']) || BizUser.where(id: params['recipients']) for example. This way we just allow selecting either User or BizUser. Of course helper should be also changed accordingly (users = User.all + BizUser.all ; users.each do ....).

If you don't want to break the existing code just create a new git branch (like git checkout -b my_branch) and work there.

Great, great. Thank you.

Loved the article! You're right, the documentation was too concise. and this is a great help. I've been having some problems implementing the trash method though. I've checked the website you deployed and it seems it's having the same problem as well i.e. can't move a conversation to trash. Currently trying to find a solution for this.

Interesting. I am going to check this one but indeed it was working when I deployed the demo. Thank you for letting me know!

Okay, got it. I've added a piece of code by mistake. conversations_controller.rb, line 2

before_action :get_mailbox, except: [:destroy, :restore]

should be

before_action :get_mailbox

because otherwise there is no mailbox and therefore nowhere to find conversation. Going to fix this.

Great Article. The code your provided works great when using it with two user models. I did run into one small problem. When using mailboxer between two user models, the user id's will conflict (ie. two user may have an id of 5). Is there any way around that?

That's interesting. Unfortunately, I haven't tried to implement such setup. Do you have your code somewhere so that I can a look?

I'm getting errors when I try to start new conversations. Can you please help me figure out exactly why this is happening?

SyntaxError app/views/messages/new.html.erb:5: syntax error, unexpected keyword_class, expecting keyword_do or '{' or '('
'.freeze;@output_buffer.safe_append='

syntax error, unexpected tSTRING_BEG, expecting keyword_end
...=( label_tag 'message[subject]', 'Subject' );@output_buffer....

Started GET "/messages/new" for 127.0.0.1 at 2015-01-21 15:52:18 -0500
Processing by MessagesController#new as HTML
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
User Load (0.0ms) SELECT "users".* FROM "users"
BizUser Load (1.0ms) SELECT "biz_users".* FROM "biz_users"
Rendered messages/new.html.erb within layouts/application (10.0ms)
Completed 500 Internal Server Error in 50ms

ActionView::Template::Error (undefined method firstuser@hotmail.com_attacher' for nil:NilClass):
13:
14: <div class="form-group">
15: <%= label_tag 'recipients', 'Choose recipients' %>
16: <%= select_tag 'recipients', recipients_options, multiple: true, class: 'form-control chosen-it' %>
17: </div>
18:
19: <%= submit_tag 'Send', class: 'btn btn-primary' %>
app/helpers/messages_helper.rb:6:in
block in recipients_options'

I'm getting these errors inside of the messages\new.html.erb form

<h1>Start Conversation</h1>

<%= 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 chosen-it' %>
    </div>

    <%= submit_tag 'Send', class: 'btn btn-primary' %>
<% end %>


def recipients_options
    s = ''
    users = User.all + BizUser.all; users.each do |user|
      s << "<option value=' #{user.id}' data-img-src='#{attachment_url(@user, user.email, :profile_avatar, :fill, 30, 30)}'> #{user.username}</option>"
    end
    s.html_safe
  end

I'm using refile gem btw for image attachments.

I can't find error in this piece of code but that may be somewhere else. Do you have your project somewhere on GitHub or is it private?

I followed the same pattern with refile gem to add functionality for user avatars. But, that approach doesn't work. Instead, I stayed with your method, and I will use refile for other elements in the application.

I wonder if there's a cleaner way to resolve the issues with adding both user models to the mailboxer system. I'm currently getting a long reload time.

UPDATE: It started loading faster. I don't know, maybe it was preparing the db. But, still, this approach of loading all users into the pull down list will become a problem when the user base grows?

Omg Ilya Bodrov you fulfilled my request. Thank you sooooo much.
Just saw the post. I am gonna try it out now.

It started loading faster. I don't know, maybe it was preparing the db.
But, still, this approach of loading all users into the pull down list
will become a problem when the user base grows?
- yes, of course. If you have lots of users that form with dropdown is going to take much time to load.

You can either use model caching or implement a bit more advanced solution with AJAX to lazy load only the records that match the entered sample. Also it is a nice idea to send AJAX request when at least two (or three maybe) letters are entered in the field. You may also employ a jQuery plugin that fires an event when user stopped typing (or create such plugin yourself as it shouldn't be hard).

Well, I liked your idea and the topic really appeared to be interesting smile

Awesome guide. I tried a few others and this is by far the best. Thanks

Thank you!