Specifying Options in Rails

03.08.08 / 3pm / Rails

The code below offers a way to solidify the “options” concept which is prevalent in Rails and Rails plugins. Its advantages are that an error is thrown when you try to specify an illegal option, rather than when the options are read; and that you can use expressions for options, consolidating option-specific code and tying it explicitly to your options.

When you want to pass your Options object to a method, like ActiveRecord’s find, use options.to_hash.

I would have liked to just pass the Options object itself, but that would require lazy evaluation, and I’m not sure how to do that with Ruby.

class Options
  def self.option_list(*options)
    attr_accessor(*options)
    define_method(:options){ options }
  end

  def to_hash
    hash = {}
    options.each do |option|
      hash[option] = self.send(option) unless self.send(option).nil?
    end
    hash
  end
end

class ActiveRecordOptions < Options
  option_list :conditions, :order, :group, :limit, :offset, :joins, :include, :select, :joins, :page, :per_page
  attr_accessor :condition_text, :condition_values, :finder

  def initialize
    @condition_text = []
    @condition_values = []
  end

  def conditions
    text = @condition_text.collect{|ct| "(#{ct})"}.join(" AND ")
    return unless text && !text.blank?
    [text, @condition_values].flatten.compact
  end
end

In the example below, a condition is built up in each if params[:search] block. I’ve found myself doing this a lot. With the ActiveRecordOptions class I can DRY up my code by specifying in one place how to take the conditions components I’ve built up and generate one value from them.

options = ActiveRecordOptions.new
options.order = "created_at DESC"

if params[:search]
  if params[:search]['start_date'] && !(params[:search]['start_date'] =~ /From/i)
    start_date = Date.parse(params[:search]['start_date'])
    options.condition_text << "created_at >= ?"
    options.condition_values << start_date
  end
  if params[:search]['end_date'] && !(params[:search]['end_date'] =~ /To/i)
    end_date = Date.parse(params[:search]['end_date']) >> 1
    options.condition_text << "created_at <= ?"
    options.condition_values << end_date
  end
  if params[:search]['user_id'] && params[:search]['user_id'].strip =~ /^\d+$/
    options.condition_text << "user_id = ?"
    options.condition_values << params[:search]['user_id'].strip
  end
end

@bulk_uploads = BulkUpload::Source.find(:all, options.to_hash)

# options.to_hash will yield something like
# {
#   :order => "created_at DESC",
#   :conditions => ["(created_at >= ?) AND (user_id = ?)", "2008-03-10", 80]
# }