Understanding Ruby and Rails: extract_options! from Arrays

This is article is part of my series Understanding Ruby and Rails. Please see the table of contents for the series to view the list of all posts.

How many times did you see a method call like the following one in your Rails application?

my_method :arg1
my_method :arg1, :arg2, :argN
my_method :arg1, :foo => true, :bar => 1

What makes my_method quite special is the ability to pass an arbitrary number of parameters (:arg1, :arg2...) followed by a list of keyword/value options.

This is made possible by a really helpful method provided by ActiveSupport called extract_options!. What this core extension does is to extract the options from the given set of arguments. When no options are available, the method returns a blank Hash.

Let me show you an example.

# Use args with the splat operation to allow
# an unlimited number of parameters
def my_method(*args)
  options = args.extract_options!
  puts "Arguments:  #{args.inspect}"
  puts "Options:    #{options.inspect}"
end

my_method(1, 2)
# Arguments:  [1, 2]
# Options:    {}

my_method(1, 2, :a => :b)
# Arguments:  [1, 2]
# Options:    {:a=>:b}

extract_options! is largely used in every Rails project and you probably encountered it countless times. It powers the most part of Rails features you use every day including ActionController filters, ActiveRecord validations and finder methods.

# ActionController filters
class MyController < ApplicationController   before_filter :my_method, :if => :execute?, :only => %w(new)
end

# ActiveRecord validations and finders
class MyModel < ActiveRecord::Base   validates_presence_of :field, :allow_blank => true

  ...

  def self.my_find
    find(:all, :order => "id", :limit => 10)
  end

end

extract_options! allows you to easily extract a list of options from an array of parameters, usually coming from a method invocation.

It isn't a standard Ruby method but a Rails Core Extension and you need to require ActiveSupport in order to use it. Also, beware that this is a "bang" method, hence it definitely modifies the object it is called on.