Thursday, October 4, 2007

acts_as_messageable plugin released!

Problem:

Private messaging in Rails.

Solution:

acts_as_messageable

What it do?

acts_as_messageable is a plugin for enabling private messaging between users. Conversations, multiple recipients, trash, sentbox... its all in there.

to install:
> script/plugin install http://actsasmessageable.googlecode.com/svn/tags/acts_as_messageable-1.0.1 
> script/generate messageable 
> rake db:migrate
to use:
class User < ActiveRecord::Base 
  acts_as_messageable 
end
sending:
phil = User.find(3123) 
todd = User.find(4141) 
phil.send_message(todd, "whats up for tonight?", "hey guy")

sends a mail to todd's inbox and puts the sent message in phil's sentbox.

retrieving:
todd.mailbox[:inbox].unread_mail

returns an array of all unread mail messages in todd's inbox.

replying:
mail = todd.mailbox[:inbox].unread_mail[0] 
todd.reply_to_sender(mail, "not sure, probably having a few dozen cocktails.")

replys to the sender of the mail message. (Messages can be sent to multiple users, and are conversation based. See the RDoc.)

moving / deleting:
convo = mail.conversation
todd.mailbox.move_to(:trash, :conversation => convo)

moves all mail messages to the 'trash' that are part of the given conversation. There are 3 default mailboxes for each user (:inbox, :sentbox, :trash), although any name can be passed to this method if you wanted to implement folders.

bonus:
todd.mailbox[:inbox].latest_mail

returns the last message received for each conversation you are involved in. Pretty sweet for an inbox view.

Check the RDoc.

37 comments:

josh said...

Phil,

Are you currently using this in a project, and if so how large? If I may ask.

Phil said...

yes I am currently using this on bandsintown.com.

Peter said...

Awesome! Thanks Phil! This is a really nice little plugin and fairly well documented. I'm trying to implement a little message system with it now and I think I'm missing something (like confusing Messages and Mail) because I can't figure out how to get individual Message objects directly from the mailbox...

I'd like to do something like this:

@messages = current_user.mailbox[:inbox].messages

but, instead I'm using the very cludgy:

current_user.mailbox[:inbox].mail.each do |mail|
@messages << Message.find(mail.message_id)
end

Can someone post an example of the best way to get an individual message or array of messages to send to an inbox or message view?

Thanks!
Peter

ps. Bandsintown.com is pretty sweet...

Phil said...

A mail is just basically a wrapper for a message which holds individual info on the message (read, trashed, mailbox). So if someone sent a message to 20 recipients there would only be 1 message created and 20 mails.

It was confusing for me building it too but I believe this is the correct approach.

I think what you are actually after are the mail objects. If you go directly to the messages then you can not see the read state.

so you could just render the mail array as a collection and access the info with:

mail.message.message
mail.message.subject
mail.read

I also use CachedModel on my messages which really speeds this up.

latest_mail() is also really nice for an inbox view if you are going with the conversation approach... similar to how gmail inbox works, only showing you the latest message for each convo you are involved in.

Im on no sleep so I hope im making sense... let me know if you have any other questions.


glad you like bandsintown.

Peter said...

Aahhh, I see... yes, that's exactly what I wanted. I'll check out CachedModel next and maybe dive into the folder & conversation features later... Thanks again!

Phil said...

no problem... glad to see youre using it.

xxx said...
This comment has been removed by the author.
xxx said...

Could you please post some examples how to write the controller and views? The stuff posted above doesn't really say anything. Nor does the Rdoc...

Phil said...

I will post some more examples later on tonight.

xxx said...

I don't want to push you or anything... ;) Put you got some examples up your sleeve? Else I move on to another plugin that I know works out-of-the-box.

Phil said...

It does work out of the box i just chose not to include controller/views because they tend to be very custom... anyway here are some examples:

example url:
-------------
#user/:id/mail/:type
/user/3/mail/inbox

controller:
-------------
def mail()
@user = User.find(params[:id])
@mail = @user.mailbox[params[:type]].latest_mail
end

view:
------------
<%= render(:partial => '/shared/mail', :collection => @mail %>

in that partial you could just access the mail info through:

mail.message.message
mail.message.subject
mail.read


Just a simple example but that should get you going.

-Phil

Aroop said...

looks cool. I am going to use it right way in my project. Will post the experience

Aroop said...

I have posted an issue that was occurring in migration

http://code.google.com/p/actsasmessageable/issues/detail?id=1

Also can you plz post the routes that you are using in your project esp the nested one.

Thanks,
Aroop

Phil said...

@aroop

Thanks for reporting that bug, I have applied a fix and the new version is available here:

http://actsasmessageable.googlecode.com/svn/tags/acts_as_messageable-1.0.1

also for the routes im using something like this:

/user/:id/mail/:mailbox_type
(for inbox, sentbox, trash, etc.)

/user/:id/convo/:convo_id
(for reading conversations)

Phil said...

here is the new version link if you can't see it in my last comment:

new version

joy said...

Phil,

Your site (BandsInTown) absolutely rocks! Congrats!

Is the acts_as_messageable plugin already Rails 2.0 compatible?

Cheers, Sazima

Phil said...

I am not sure, I havent made the switch yet. If you find out please let me know.

Saza said...

Phil,

Apparently "acts_as_messageable" is Rails 2.0.2 compatible!

But unfortunately I'm very confused on how to use it...

Any chance you could post or send me an example application (basically routes, controllers and views)?

Cheers, Sazima

Celular said...

Hello. This post is likeable, and your blog is very interesting, congratulations :-). I will add in my blogroll =). If possible gives a last there on my blog, it is about the Celular, I hope you enjoy. The address is http://telefone-celular-brasil.blogspot.com. A hug.

dmarc said...

Phil,

This is a wicked plugin, great job!!! I am however, trying to implement this plugin along with the Goldberg authentication plugin, and running into problems. The Goldberg plugin has a user table, with a user.rb which includes a Goldberg module. I basically tried to change reference class_names from User to Goldberg::User and encounter an error stating 'undefined method 'mail' for Goldberg::User:class..I also tried to create a user.rb with the following inheritance
User < Goldberg::User and get the same error..but stating 'undefined method 'mail' for User:Class?
Is this due to the line in the mail.rb? self.table_name = "mail", any clues on how to integrate both.

Thanks, in advance,
Dmarc

Mustafa Ekim said...

Phil, it is a good plugin. good work.

I wonder if it is possible that you share your views also. I know views could vary very much but you know, it is messaging, it will be almost same everywhere, if not it will present a good example for the people.

simply the views in bandsintown.com will do..

thanks!

MB said...
This comment has been removed by the author.
mickey said...

I am trying to get this plugin to work within a rails 2.0.2 application but i run into a weird problem.

When trying to access the mailbox and its mails from the rails console everything works as expected:

u = User.find(:first)
mb = u.mailbox[:inbox]
mb.mail.each { |mail| puts mail.message.subject }

But when used within a controller/view in a similar way only the first call works.

Every call after throws

undefined method `message' for #< Mail:0x6878614 >

Any Ideas whats going wrong?

Tashfeen Ekram said...

Does your plugin integrate with will_paginate?

Phil said...

hmm I have not actually tried it with that plugin, but I believe that will_paginate just wraps find so I dont see why it would not work.

Let me know if you try it.

Dave L said...

I am also using the rails 2.0.2 environment and am getting the same undefined method `message' for #< Mail:0x6878614 > error as described above. It works if I set @mail to the first element of the array, but not when I try and iterate through the array in the view. Has this problem been addressed. Does it work in rails 2.0 and above? Thanks, this looks like a great plugin!

Phil said...

can you paste an example of what you are trying to do? I am currently using this with rails 2.1 without any issues.

Dave L said...

Hey Phil, I actually just got it working and it seems to be functioning without a hitch. Thanks though!

Dave L said...

Hey, everything is working great. Better than I anticipated. I am trying to add another couple fields to the mail object so that I can define a message type(message type and message_type_id), so that I can display icons for different message types in the inbox(i.e. a request versus just a message). I have been trying to decipher the code to find how a mail object is saved and I get lost. Any advice for the first step?

Kusti said...

Hi,

I'd like to use this plugin in my project. The only difference is that instead of a model named 'User' I need to have a model named 'Person'. So I have modified the plugin a bit (changing user_id to person_id from Mail model, and all usages of User class point to Person).

However, something still seems to be broken. When I put the line 'acts_as_messageable' in my Person model, I get an error when I try to access the view in a browser. The error states: "undefined local variable or method `acts_as_messageable' for #Class:0x20594b4". Do you have any idea what might cause this? Is there any additional changes I need to perform in order to get the plugin to work in my Person model?

robbie said...

Phil, reply_to_conversation throws errors in Rails 2.1.

ArgumentError: Unknown key(s): order

I traced this down to the mail_count method which does add_conversation_condition, line 299 is the offender.

I overrode the has_conversation? method with:

#return mail_count(:all, :conversation => conversation) != 0

return Mail.exists?(['user_id = ? and conversation_id = ? and mailbox = ?', @user.id, conversation.id, @type.to_s])

The error went away, and it "seems" to give me the result I need. Is there a better way of handling this error, and am I missing any conditions/options in my version?

Christopher said...

I love the implementation on your site. I was wondering if you had come across anyone who had written a short write up about using this plugin. I would love to be able to use it in my project but as a rails beginner I'm not sure how to set up the controllers, views, and routes.

I know you have provided some examples but have you come across anyone who had written a little more in depth?

Anyways, thanks for sharing this great code!

Olivier said...

Hello Phil,

First thanks for this realy nice plugin.

I am currently using it in one of my projects but I have this error and i can't figure out how to correct it.

Here is the error :

Mail(#25465460) expected, got Mail(#49802630)

Maybe you'll have an idea.

Thanks in adavance.

Olivier

Abhishek said...

i m trying to use this..
but as shown i tried
mail = someuser.mailbox[:inbox].latest_mail

&then i write
mail.message.message
mail.message.subject

but everytime it is giving error that undefined method "message" or "subject"


how to resolve that.plz reply

Kevin T said...

Abhishek, mail is an array of mails. Try mail[0] or put it into a loop.

Dariusz said...

Hi Phil, did You test it in rails3 ?

Kevin T said...

I'm using this with Rails 3. I had to go through and change all of the calls to the "Mail" database to be "Mail2" instead since it seems like "Mail" is reserved in Rails 3.

After making these changes, I have been using it in Rails 3 and 3.1 for 8 months. Now I started using asset pipeline and there is a new problem:

Expected acts_as_messageable-1.0.1/lib/mailbox.rb to define Mailbox

So now I will fix that, hopefully. I hope it is easy.