Best way to do task in background in Rails


#1

Hey everyone,

I’m still getting up to speed with Rails and Ruby. Is there a best
practice technique for kicking off a thread to work on an activity in
the background?

Here’s the scenario: the user performs an action that triggers e-mails
to be sent. (I’m using ActionMailer). My app acknowledges with a message
to the user that the messages have been sent. I would like to show the
acknowledgment right away instead of the noticable delay I’m seeing now
depending on the number of recipients.

In my ActionController I do this:

def send_invites ... thread = Thread.new(e) do |event| EventMailer.send_invites(event) end thread.join ### this is what I'm curious about! ... end

I’ve tested my code with Webrick and lighttpd on the Mac (Rails 1.1).
I’ve found that without the thread.join, the code doesn’t work–the
EventMailer doesn’t get to do its work. From what I’ve read in the
pickaxe book, I think it’s because the spawned thread is killed when the
invoking (Rails) thread exits. The code works with the join, but this
defeats the purpose of what I’m trying to do as the UI thread blocks.

Am I taking the right approach here? Will my results be different under
Apache/FastCGI?

I came across DRb and Rinda when I did a quick search. Because I don’t
need to communicate with the background task, I’d prefer a really simple
approach before I investigate DRb, Rinda or something else.

Thanks in advance for advice and feedback!

–Ed Lau


#2

Hi !

2006/5/1, Ed Lau removed_email_address@domain.invalid:

def send_invites ... thread = Thread.new(e) do |event| EventMailer.send_invites(event) end thread.join ### this is what I'm curious about! ... end

Look into rails_cron or plain cron. In your controller, mark the
event as ready to send, and then use a class method in Event to do the
send:

class Event
def sent?
self.sent_at
end

def send!
return if self.sent?
self.class.transaction do
EventMailer.send_invites(self)
self.sent_at = Time.now
self.save!
end
end

def self.send_ready
self.find_ready.send!
end
end

You can then run this code using script/runner:

crontab -l

          • /u/me/apps/rails-app/script/runner -e production
            “Event.send_ready”

Using rails_cron:

class Event
background :send_ready, :every => 1.minute
end

Hope that helps !