Specifying Options in Rails

Posted: March 8th, 2008 | Author: Daniel Higginbotham | Filed under: Rails | 3 Comments »

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]
# }

3 Comments on “Specifying Options in Rails”

  1. 1 Greg Weber said at 12:04 pm on March 25th, 2008:

    seems like you are reinventing the Struct, (but adding to_hash).

  2. 2 Daniel Higginbotham said at 3:36 pm on March 25th, 2008:

    That does look very much like a Struct :) I started out with something different, and in changing it I didn’t realize the similarity.

    One of the useful parts of my code is the conditions method in ActiveRecordOptions , which then gets called by to_hash. Can Structs do this or something similar by default? That would be helpful.

    Using the Options class might still be a better choice because its purpose is more explicit. So perhaps it is a reinvention, but so far I find it a useful reinvention :)

  3. 3 Daniel Higginbotham said at 3:59 pm on March 25th, 2008:

    On the other hand, it might be confusing to have a new Options class because perceptive people like yourself will look at it and think the same thing you did, and wonder what the point is. In either case, I’ve found that using something a non-primitive object like this is useful when you’re building up options over multiple methods.

    Doing so helps keep things DRY, reduces failure distance, and communicates more explicitly.


Leave a Reply