1

Topic: HOWTO: Send Instant Messages from Rails

UPDATED for BackgrounDRb 0.2.1

In this tutorial we are going to use a BackgrounDRb worker to hold an IM client. Our Rails app will then use BackgrounDRb to tell the IM client to send instant messages.

To help put this example in the context of a real-world application, let's imagine that we are working on a newspaper website. Whenever a new article is created we want to notify readers by sending them an Instant Message.

Let's roll

Let's get started by creating a small Rails application. I'm assuming you are familiar with using scaffolding to build CRUD functionality so we are going to go through this part quickly.

1) Create a Rails application called dailynews.

saturn:~ bp$ rails dailynews
[...]
saturn:~ bp$ cd dailynews

2) Create a database and modify config/database.yml appropriately.

saturn:~/dailynews bp$ echo "create database dailynews" | mysql -u root -p

3) Build the Article model and edit the migration created in the last line of output.

saturn:~/dailynews bp$ ./script/generate model Article
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/article.rb
      create  test/unit/article_test.rb
      create  test/fixtures/articles.yml
      create  db/migrate
      create  db/migrate/001_create_articles.rb

Let's keep it simple and only give an Article a title and a body. db/migrate/001_create_articles.rb:

class CreateArticles < ActiveRecord::Migration
  def self.up
    create_table :articles do |t|
      t.column :title, :string
      t.column :body, :text
    end
  end

  def self.down
    drop_table :articles
  end
end


4) Migrate to build the articles table.

saturn:~/dailynews bp$ rake db:migrate
(in /Users/bp/dailynews)
== CreateArticles: migrating==================================================
-- create_table(:articles)
   -> 0.1340s
== CreateArticles: migrated (0.1357s)=========================================

6) Generate scaffolding to get some CRUD goodness going.

saturn:~/dailynews bp$ ./script/generate scaffold Article

7) Start the server and check to see that things are working.

saturn:~/dailynews bp$ ./script/server

If everything went as planned we should be able go to http://localhost:3000/articles/new and create our first article. Maybe something about the weather?

Instant Message Me!

Ok, now for some IM fun. XMPP4R is a Ruby XMPP/Jabber library. Grab it and install. I'm using xmpp4r-0.3, which you can grab here: http://download.gna.org/xmpp4r/xmpp4r-0.3.tgz Installation is straightforward. Just unpack the archive and run install.rb. Like this (output removed for brevity):

saturn:~/dailynews bp$ cd /tmp

saturn:/tmp bp$ wget http://download.gna.org/xmpp4r/xmpp4r-0.3.tgz

saturn:/tmp bp$ tar -zxvf xmpp4r-0.3.tgz

saturn:/tmp bp$ cd xmpp4r-0.3

saturn:/tmp/xmpp4r-0.3 bp$ sudo ./setup.rb


A quick test with irb to make sure we have it installed.

saturn:/tmp/xmpp4r-0.3 bp$ irb
irb(main):001:0> require 'XMPP4R'
=> true
irb(main):002:0> quit

The line that says "=> true" means we have it. Back to our Rails app to start sending IMs.

saturn:/tmp/xmpp4r-0.3 bp$ cd ~/dailynews/

We are going to need two IM accounts (that speak XMPP/Jabber) to make this work. You could probably do it with one, but that gets a little confusing. If you have a Gmail account you already have a Google Talk account (http://www.google.com/talk/). I got another by registering an account on xmpp.us. I wasn't able to get iChat to register the new account. For that I used Fire (http://fire.sourceforge.net/). Don't worry about following the same steps I did to get your accounts setup. The important part is that you have two accounts.

The Setup

I'm on OSX using iChat to connect to my Google Talk account. The Rails app will use then use the xmpp.us account to send messages to my Google Talk account. To help keep things clear let's call the account Rails will use
rails@xmpp.us and the other human@google.com (Someone might actually own these accounts so please don't use them when testing your code).

Since we want to send IMs when a new article is created, let's define an after_create method in the Article model. This is article.rb:

require 'XMPP4R'
class Article < ActiveRecord::Base
 
  def after_create
    # Connect to the server and set presence to available for chat.
    jid = Jabber::JID.new('rails@xmpp.us/dailynews')
    client = Jabber::Client.new(jid, false)
    client.connect
    client.auth('railsxmpppassword')
    client.send(Jabber::Presence.new.set_show(:chat).set_status('Rails!'))
   
    # Send an Instant Message.
    body = 'Hello from Rails'
    to_jid = Jabber::JID.new('human@gmail.com')
    message = Jabber::Message::new(to_jid, body).set_type(:normal).set_id('1')
    client.send(message)
  end
 
end

The first line imports the XMPP4R library that we then use to send the IM. In after_create we connect to the server, authenticate ourselves, set our presence and then send a message. Now when you create a new article you should get an IM saying "Hello from Rails." Check out the XMPP4R website for more info on the library and rdoc documentation.

Mission Accomplished?

Well, we did send an IM from our Rails application, so yes. But there is something about it that doesn't feel quite right. It seems cumbersome to have to login to the rails@xmpp.us account every time we want to send a message. Instead we ought to login and stay logged in between requests. Then we can fire off IMs easily. Enter BackgrounDRb.

If you haven't heard of BackgrounDRb, it is a plugin that "facilitates running background tasks in a separate process from Rails, thereby decoupling them from the request/response cycle." Awesome! BackgounDRb is typically used for long-running tasks that eventually finish, like uploading a large file. But what about a really really long task? Like one that never finishes? Like an IM client!

First things first. We need to install the BackgrounDRb plugin and get it set up. (NOTE: Requires Slave 1.1.0 or higher and Daemons 1.0.2 or higher.) I've trimmed all of the output except the last line to show which revision I'm using:

saturn:~/dailynews bp$ cd vendor/plugins/
saturn:~/dailynews/vendor/plugins bp$ svn co http://svn.devjavu.com/backgroundrb/tags/release-0.2.1 backgroundrb
[...]
Checked out revision 165.
saturn:~/dailynews/vendor/plugins bp$ cd ~/dailynews/

We set it up with a rake task which copies files where they need to be.

saturn:~/dailynews bp$ rake backgroundrb:setup
(in /Users/bp/dailynews)
Copying backgroundrb.yml config file to /Users/bp/Sites/examples/newsbot/config/../config/backgroundrb.yml
Copying backgroundrb script to /Users/bp/Sites/examples/newsbot/config/../script/backgroundrb

Take a look at config/backgroundrb.yml. I left everything as is. If you are running a firewall be sure that port 2000 is open or change the port to something else that is.

Now we will create a BackgrounDRb worker that will house our IM client. There is a generator that will create the necessary files. Let's call it Alert because it will eventually be sending news alerts.

saturn:~/dailynews bp$ ./script/generate worker Alert 
      exists  lib/workers/
      create  lib/workers/alert_worker.rb

BackgrounDRb workers come ready-made with a do_work method. This method is called when you instantiate a new worker instance from Rails. We'll use this method to login to the rails@xmpp.us account.

To send the IMs we'll use another method, send_alert, that will take the title of an Article as an argument.

require 'XMPP4R'
class AlertWorker < BackgrounDRb::Worker::RailsBase
 
  def do_work(args)
    jid = Jabber::JID.new('rails@xmpp.us/dailynews')
    @client = Jabber::Client.new(jid, false)
    @client.connect
    @client.auth('railsxmpppassword')
    @client.send(Jabber::Presence.new.set_show(:chat).set_status('BackgrounDRb!'))
    loop do
      @client.process
      sleep(1)
    end
  end

  def send_alert(title)
    to_jid = Jabber::JID.new('human@gmail.com')
    message = Jabber::Message::new(to_jid, title).set_type(:normal).set_id('1')
    @client.send(message)
  end

end
AlertWorker.register


This is very similar to the way we sent an IM in the after_create method. The main difference is that we are breaking the connection and sending an IM into two parts. In the do_work method we use an instance variable to hold the client. That allows the send_alert method to use it later on.

We only want one BackgrounDRb worker for our Rails application and we'll do this by setting the job_key when we instantiate the worker for the first time and then using the same job_key to access the worker later. Since we want this worker to be running all the time, we'll instantiate it in app/controllers/application.rb.

class ApplicationController < ActionController::Base
 
  unless MiddleMan[:alerter]
    MiddleMan.new_worker(:class => :alert_worker, :job_key => :alerter)
  end
 
end

MiddleMan is the bridge between Rails and BackgounDRb. Here we use it to instantiate a new worker. Later on we will use it to access the same worker.

Start Your Engines

This part is a little tricky. Be sure that you start your Rails server, then BackgrounDRb, and then hit the web page. Like so:

saturn:~/dailynews bp$ ./script/server

Then from a different console:

saturn:~/dailynews bp$ ./script/backgroundrb start

Now hit a page like http://localhost:3000/articles/. The worker should now be running in BackgrounDRb and rails@xmpp.us should be logged in.

If you are having any trouble with BackgrounDRb keep in mind that any change you make to a worker will not take effect until you restart BackgrounDRb. Another thing to do if you are getting strange behavior is stop all ruby processes and start them up again. I had an errant process that caused me some "I swear I changed the right file" confusion.

Send It!

We are so very close now! The final piece is to change the after_create method in the Article model to send an IM via the worker.

def after_create
 
  MiddleMan.worker(:alerter).send_alert(self.title)
 
end

Now create a new article and, if everything works and the stars are aligned,  you should get an IM containing the article title. Yay!

I hope you had fun with this and I encourage you to play around and experiment. What else could we do with this? I'm wondering about communication going the other direction. Possibly send commands to your Rails app from iChat? What else?

References

http://backgroundrb.rubyforge.org/
http://www.infoq.com/articles/BackgrounDRb
http://home.gna.org/xmpp4r/

Last edited by bp (2006-12-11 00:22:34)

2

Re: HOWTO: Send Instant Messages from Rails

BackgrounDRb 0.2.0 was released today. A full rewrite. I haven't tried it out yet. Different svn repository so hopefully there won't be any problems installing the way I did above.

http://brainspl.at/articles/2006/10/30/ … 0-released

Re: HOWTO: Send Instant Messages from Rails

Great tutorial, everything explained step by step (although I have not tried this).
And the BackgrounDRb is just so simple and great  smile

4

Re: HOWTO: Send Instant Messages from Rails

Thanks! Glad you liked it. I was thinking of posting the code for people to play around with. If you (or anyone) is interested, let me know and I'll get it up somewhere.

Re: HOWTO: Send Instant Messages from Rails

Very good tutorial!

Re: HOWTO: Send Instant Messages from Rails

thanx a lot for this great tutorial.

bp wrote:

Thanks! Glad you liked it. I was thinking of posting the code for people to play around with. If you (or anyone) is interested, let me know and I'll get it up somewhere.

yes please put it online somewher or send it to my email patcito at gmail dot com smile

7

Re: HOWTO: Send Instant Messages from Rails

I'll try to get it up in the next couple days.

Re: HOWTO: Send Instant Messages from Rails

what if

1. saturn:/tmp/xmpp4r-0.3 bp$ irb
   2. irb(main):001:0> require 'XMPP4R'
   3. => true
   4. irb(main):002:0> quit

does not work..!
error:

LoadError: no such file to load -- XMPP4R

9

Re: HOWTO: Send Instant Messages from Rails

Did you run setup.rb?

Re: HOWTO: Send Instant Messages from Rails

I have to say bp that this tutorial is awesome.  I hope to be able to play around with it tonight and see what I can do with it.

Seriously, this rocks.

11

Re: HOWTO: Send Instant Messages from Rails

Thanks danger! I hope you have a chance to play with it and I'd love to hear how it goes.

I made a tarball of the code without vendor/rails (trying to keep it small). Here it is for anyone that wants it.

http://www.bradpauly.com/code/dailynews.tar.gz

Re: HOWTO: Send Instant Messages from Rails

This is a great tutorial! I wish I had it when I started investigating XMPP and rails...

I recently started a series of posts about XMPP and ruby/rails. It's not tutorial style and goes a bit deeper in to the protocol and usage. So far I posted 3 parts:

1. Introduction
2. Logging in and sending simple messages
3. Adding rich text to messages

The third one deals with html in messages which is not really well documented outside of the standard documents, so I hope my post will help facilitate using it.

I'm also in the process of writing more posts about it, including queries, presence and using gateways to other services like YM, ICQ, AIM and MSN.

13

Re: HOWTO: Send Instant Messages from Rails

I'm glad you liked it. Great series you have too! I look forward to more.

Also a quick note on this tutorial. I received an email from Olle Jonsson, including line by line edits (thanks Olle!), letting me know that the latest version of BackgrounDRb, 0.2.1, includes some changes that make this tutorial incorrect. I hope to grab it and make appropriate updates later tonight or tomorrow.

Last edited by bp (2006-12-10 19:18:25)

Re: HOWTO: Send Instant Messages from Rails

bp wrote:

I'm glad you liked it. Great series you have too! I look forward to more.

Also a quick note on this tutorial. I received an email from Olle Jonsson, including line by line edits (thanks Olle!), letting me know that the latest version of BackgrounDRb, 0.2.1, includes some changes that make this tutorial incorrect. I hope to grab it and make appropriate updates later tonight or tomorrow.

One thing I'd recommend is also to look at daemon generator. It's a MUCH simpler system than BackgroundDB, but it's also EXTREMLY simple and adds very little code. I use it for sending alarms in Famundo to IM. It sleeps for some time, check the DB for alarms, send the ones triggered, and go back to sleep. It was stupid easy with daemon_generator.

Re: HOWTO: Send Instant Messages from Rails

LoadError: no such file to load -- XMPP4R

I have run the setup.rb. But it seems that I can not load the file:XMPP4R.

Would you kindly please help me? Thanks for your tutorial!

16

Re: HOWTO: Send Instant Messages from Rails

yayes, sorry for the late response. Did you get this figured out?

Re: HOWTO: Send Instant Messages from Rails

yayes, try small letters. For me works ok.

require 'xmpp4r'

bp, great tutorial, thanks.

Re: HOWTO: Send Instant Messages from Rails

I'm receiving an error like this:

nando@dokie:~/Desktop/messenger$ script/backgroundrb start
/usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require': no such file to load -- slave (LoadError)

Do I need to install anything else?

UPDATE: Never mind, just "sudo gem install slave" did the trick

Last edited by fnando (2007-06-23 21:01:54)

Re: HOWTO: Send Instant Messages from Rails

If I wanted to make a simple Jabber client using Rails. Let's say I just want to be able to send and receive messages using xmpp4r-simple...do I really need to go through the trouble of running a seperate process with backgroundrb?

Here is the awesome xmpp4r-simple library: http://xmpp4r-simple.rubyforge.org/

This seems like there should be an easier way to handle this than having to deal with backgroundrb. Why can't I do something like simply create the Jabber connection:

session[:jabber] = Jabber::Simple.new('name@sample.com', 'password')

and then simply use a periodical executer to call an action for receiving messages

periodically_call_remote(:url => '/jabberupdateurl', :frequency => 10)

There has got to be some way of maintaining a simple Jabber session through multiple requests and then simply checking ever x seconds for updated messages. XMPP4R-simple already does all the work for you. I need to be able to do this:

@im = Jabber::Simple.new('name@sample.com', 'password')

and then call

puts @im.received_messages

from a periodic request.

The issue is keeping that @im or whatever around for multiple requests because I can't relogin every time.

Please, can someone help me?

Last edited by xgamerx (2007-07-24 04:09:56)

Re: HOWTO: Send Instant Messages from Rails

Great tutorial.. Is there plugin available to do the things you just described..

I am looking for backgroundrb for sending mails ....