I ran into a really strange case yesterday while working to move an app from bj to delayed_job. I won't spend much time going into the details about why we're making this switch, but suffice to say that we had a problem similar to the one GitHub describes in their blog post. The problem is that bj reloads the entire Rails stack for every request, which is terribly inefficient. Imagine if you had to restart your web browser every time you went to a new page or submitted a form. You'd be paying a "startup tax" to launch your browser with every single request. It doesn't make sense architecturally, and it absolutely kills your CPU. The delayed_job plugin operates by leaving a single Rails instance open and available for processing requests asynchronously. It's proven to be much faster in my limited testing.
In making the move to delayed_job, I checked out the readme, which suggests structuring things like so:
class NewsletterJob < Struct.new(:text, :emails) def perform emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) } end end Delayed::Job.enqueue NewsletterJob.new('lorem ipsum...', Customers.find(:all).collect(&:email))
The idea here is that you can use a Struct to quickly create a class with a method named perform. When you enqueue a job for later, the perform method will be called with the parameters you provided. However cool this may be, it introduces a really interesting gotcha that I ran into almost immediately.
If your app has a Group model, you won't be able to use it within your perform method.
Why is that? Because of the way that Ruby namespaces work, the etc module, and the fact that something called Struct::Group already exists in your Rails app.
Perhaps a code example will help explain how this could happen:
require 'etc' # in Rails, rails/railties/lib/rails/mongrel_server requires 'etc' class Group def foo puts "hello" end end class WTF < Struct.new(:whatever) def foo Group.new.foo end end Group.new.foo WTF.new.foo # OUTPUT # # hello # NoMethodError: undefined method ‘foo’ for #<struct Struct::Group name=nil, passwd=nil, gid=nil, mem=nil>
The Group.new.foo call will work as expected, but the WTF.new.foo call will fail because it's calling the foo method on Struct::Group, which (surprisingly enough) exists, and doesn't have a method named foo. It exists because Rails has required the 'etc' module. This creates a couple of Structs on your behalf, which is the source of our problem.
Luckily, there's an easy workaround. If you prefix your calls to Group with two colons, you'll get access to the Group class that you expect. In our example, the perform method in WTF would be changed like so:
class WTF < Struct.new(:whatever) def foo ::Group.new.foo end end
Totally weird. I know.





Thanks for the heads-up.
How is
class NewsletterJob < Struct.new(:text, :emails)
end
different from
class NewsletterJob
attr_accessor :text, :emails
end
?