Archive for August, 2007

BackgrounDRb Notes

Sunday, August 12th, 2007

UPDATE: Since this post, a new version of BackgrounDRb has been released, and as such, some, if not most or all of this information should be considered outdated. Unfortunately, I haven't had a chance to check out the new version, but I'll update this page if I end up installing it.

BackgrounDRb is a great Ruby-based scheduler, but its documentation isn't quite as mature as I'd like, so I wanted to jot down some notes to help anyone who's been trying (and possibly failing) to get everything working correctly.

Note #1: You may create a file called config/backgroundrb_schedules.yml to set up default schedules when your BackgrounDRb service starts. This is not to be confused with the -c command-line switch, which is meant to point to the location of your backgroundrb.yml file (its default location is config/backgroundrb.yml).

Note #1a: If you choose to create the above-mentioned YAML file, its format might confuse you a bit, if you're accustomed to Rails' database.yml file or its fixture YAML files: You must prepend each line (except schedule names) with a colon, since BackgrounDRb accesses its YAML variables through symbols. For example:

# The following line should NOT be prepended with a colon (schedule name)
my_worker:
  # Each value inside our my_worker key SHOULD be prepended with a colon
  :class: MyWorker
  :job_key: :my_worker_key

Note #2: If you would like a schedule to repeat at a certain interval, include the :repeat_interval key beneath your :trigger_args parameter (you can't use this parameter with the :cron_trigger trigger type because repeat intervals are built into the cron structure). If you're using a YAML configuration file, it will look something like this:

my_worker:
  :class: MyWorker
  :job_key: :my_worker_key
  :worker_method: :do_work
  :trigger_args:
  :repeat_interval: 2.minutes

The :repeat_interval parameter accepts an integer, in seconds. Since BackgroundDRb is a Rails plugin, you can freely use Rails' time methods (seconds(), minutes(), hours(), etc.) to better format your number. The current (as of August 13, 2007) BackgrounDRb documentation describes :repeat_interval at times, and :interval other times. Setting the :interval key will do nothing.

Note #3: Any call to the logger object from within your workers gets put, by default, into logs/backgroundrb.log. All server calls (startup messages and trigger executions) get logged to logs/backgroundrb_server.log. This may seem trivial, but you could be racking your brain wondering why your worker isn't properly logging its responses. It most likely is, but maybe not in the log file at which you are looking.

Note #4: The BackgrounDRb documentation chooses to access worker classes by their underscored symbol variants (e.g., the MyWorker class would be referenced as :my_worker). You can choose to access your workers by their true class names (e.g., MyWorker) for verbosity's sake.I may add more notes as I come across other gotchas. Check back occasionally for updates.

Singleton can't be dumped

Sunday, August 12th, 2007

I ran into a new error today, and after digging around my code for a bit, I figured out what was going on. I have an after_filter in my ApplicationController called set_last_url. It came from the session handling code in active_record_store, and its contents look like this:

class ApplicationController < ActionController::Base
  after_filter :set_last_url

  def set_last_url
    session[:last_url] = params
  end
end

For normal purposes, this method should be fine. Its purpose is to capture the last place you visited, and redirect you back after logging in or creating a new account. However, the params hash doesn't always contain information you'd like to store in the session object. In my case, it was an uploaded file. Here is the error along with a snippet of the backtrace:

Processing ItemsController#create (for 127.0.0.1 at 2007-08-12 16:09:38) [POST]
Session ID: 7797d006f6d7668e81a16564043e064f
Parameters: {"action"=>"create", "list_id"=>"1", "controller"=>"items", 
             "item"=>{"name"=>"Futurama", "price"=>"1.00", "url"=>"http://www.futurama.com", 
             "description"=>"DVD Set", "image"=>#}}

TypeError (singleton can't be dumped):
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session/active_record_store.rb:83:in `dump'
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session/active_record_store.rb:83:in `marshal'
    /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/session/active_record_store.rb:135:in `marshal_data!'
    …

I had two options that became readily apparent: I could change my code such that I only store the controller, action, and possibly ID parameters into the session object. Unfortunately, sometimes I add parameters to the end of my query string, and I don't want to omit them from the redirect URL. The other option was to act on the request method, which is what I ended up doing. In practice, I realized I didn't need to set the last URL after calls that were anything except GET, so I edited my filter definition a bit:

class ApplicationController < ActionController::Base
  after_filter :set_last_url

  def set_last_url
    session[:last_url] = params if request.get?
  end
end

No more error!

Save vs. Save!

Wednesday, August 8th, 2007

When learning about ActiveRecord objects, two methods usually taught are ActiveRecord::Base#save and ActiveRecord::Base#create. For beginners–especially those new to Ruby as a language–these methods are fine and unobtrusive. I'm finding myself using their counterparts, ActiveRecord::Base#save! and ActiveRecord::Base#create!, respectively as time goes on, for a few reasons.

First, a bit of an explanation of what the exclamation mark at the end of a method tells you, since it's generally different in Ruby and in ActiveRecord. In general, when talking about built-in Ruby methods, the exclamation mark tells you, "This method will return the object it was passed, edited in place." Take this example:

>> str = ' A phrase with spaces '
=> " A phrase with spaces "
>> str.strip
=> "A phrase with spaces"
>> str
=> " A phrase with spaces "
>> str.strip!
=> "A phrase with spaces"
>> str
=> "A phrase with spaces"

The String#strip method removes leading and trailing spaces from a string and returns the edited string, without editing the string object itself. Conversely, the String#strip! method also removes leading and trailing spaces from a string, but it also edits the string object itself, then returns it. You can see that subsequently displaying the str object after the call to str.strip! returns the stripped string; the string object has been edited and saved, as opposed to just edited and returned, with str.strip.

ActiveRecord handles the exclamation mark differently than Ruby, generally speaking. When we think of methods with an exclamation mark at the end in methods such as ActiveRecord::Base#save! and ActiveRecord::Base#create!, what we are told is, "Try to save or create this ActiveRecord object, but if you can't for some reason (usually validation), raise an exception." In contrast, the ActiveRecord::Base#save and ActiveRecord::Base#create methods will return true if the object was successfully saved or created, and false if not.

Why would you choose to have ActiveRecord raise an exception when trying to create an object? Here are a couple reasons:

  • Using a Rails plugin like exception_notification, you can be alerted by email if an object was not saved or created for some reason. If you have code that's silently returning false when trying to save an object, you might not notice an issue with your code. This can lead to database inconsistencies and unhappy users.
  • When testing, sometimes you have a method handle a lengthy process, such as importing a record from an external source, massaging the data, and creating various objects in the database to correspond with the incoming data. If any one of these things fails, your encapsulating method might not let you know, but if you're raising exceptions, you can do something like this:
assert_nothing_raised do
  Person.import_from_external_source
end

If anything fails, you'll get a test failure and you can delve deeper into your code to figure out what's going wrong.

I use ActiveRecord::Base#save! and ActiveRecord::Base#create! when I want to make sure an object will be created, and their non-exclaiming counterparts when it's not critical that the records be updated or created (e.g., if we're importing data for an existing person and an "Email" record already exists for the person for the specified email address, we can just test for failure based on our validations and move on, without re-importing the record).