Space Vatican

Ramblings of a curious coder

With_options for Fun and Profit

Active Support has a nifty little helper that can cut down on repetition. A lot of things in Rails like validations, associations, named_scopes, routes etc… take a hash of options as their final parameter. There are times where you use many of these with some common options, for example

1
2
3
4
5
class Customer < ActiveRecord::Base
  validates_presence_of :phone_number, :if => :extended_signup
  validates_presence_of :job_title, :if => :extended_signup
  validates_presence_of :job_industry, :if => :extended_signup
end

or maybe you have a bunch of associations which all share an association proxy extension module and some settings

1
2
3
4
5
class Customer < ActiveRecord::Base
  has_many :foos, :extend =>MyModule, :order => "updated_at desc", :conditions => {:active => true}
  has_many :bars, :extend =>MyModule, :order => "updated_at desc"
  has_many :things, :extend =>MyModule, :order => "updated_at desc"
end

Enter with_options!

1
2
3
4
5
6
7
class Customer < ActiveRecord::Base
  with_options :extend => MyModule, :order => "updated_at desc" do |options|
    options.has_many :foos, :conditions => {:active => true}
    options.has_many :bars
    options.has_many :things
  end
end

Any time you’ve got a bunch of method calls taking some common options, with_options can help.

So how does it work on the inside? All the with_options method actually does is yield a special object to its block - all the craftiness is in that object. What we want that object to do is forward method calls to the object we’re really interested in (in this case the Customer class) adding the options before it does so.

As with many such proxy objects we undefine just about every method and just implement method_missing. The implementation of method_missing inspects the arguments and merges any options present with the common set defined by the call to with_options (so individual methods can take extra options or override the common ones) before passing them onto the “real” object.

Originally a limitation was that the last argument just had to be a hash, so for example if you had a procedural named_scope then with_options couldn’t work. Luckily a recent commit rectifies this: if the thing you’re trying to merge with is a proc, then with_options will replace it with a new proc that merges the common options with the result of the call to the original proc. If you’re not on edge you’ll have to wait for 2.3 in order to get this.

While both the examples I gave showed using with_options on what is essentially model class configuration it is by no means limited to that. You could use it for that sort of configuration on your own classes or just inside a regular method - anytime you are making several method calls on the same object with a hash of options at the end.