This page contains automated test results for code from O'Reilly's Ruby Cookbook. If this code looks interesting or useful, you might want to buy the whole book.

Building Queries Programmatically
CodeExpectedActual
require 'cookbook_dbconnect'
class ActiveRecord::Base 
  def self.find_by_map(id, args={}.freeze)
    sql = []
    values = []
    args[:conditions].each do |field, value|
      sql << "#{field} = ?"
      values << value
    end if args[:conditions]
    args[:conditions] = [sql.join(' AND '), values]
    find(id, args)
  end
end
activerecord_connect
class BlogPost < ActiveRecord::Base
end
BlogPost.create(:title => 'Game Review: Foosball Carnage',
                :content => 'Four stars!')
BlogPost.create(:title => 'Movie Review: Foosball Carnage: The Movie', 
                :content => 'Zero stars!')
BlogPost.find_by_map(:first, 
                     :conditions => {:title => 
	                             'Game Review: Foosball Carnage' }
                    ).content
"Four stars!"
Error! (Exception?) Here's stdout:
TypeError: singleton method called for a different object
	from /usr/lib/ruby/gems/1.8/gems/facets_more-1.0.2/lib/facet/basicobject.rb:96:in `bind'
	from /usr/lib/ruby/gems/1.8/gems/facets_more-1.0.2/lib/facet/basicobject.rb:96:in `method_added'
	from /usr/lib/ruby/gems/1.8/gems/facets_more-1.0.2/lib/facet/basicobject.rb:95
	from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:21:in `require'
	from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.0/lib/active_support/dependencies.rb:136:in `require'
	from /usr/lib/ruby/gems/1.8/gems/facets_more-1.0.2/lib/facet/openobject.rb:51
	from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:21:in `require'
	from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.0/lib/active_support/dependencies.rb:136:in `require'
	from /usr/lib/ruby/gems/1.8/gems/facets_more-1.0.2/lib/facet/annotation.rb:53
	from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:21:in `require'
	from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.0/lib/active_support/dependencies.rb:136:in `require'
	from /usr/lib/ruby/gems/1.8/gems/glue-0.28.0/lib/glue/property.rb:1
	from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:21:in `require'
	from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.0/lib/active_support/dependencies.rb:136:in `require'
	from /usr/lib/ruby/gems/1.8/gems/glue-0.28.0/lib/glue.rb:18
	from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:21:in `require'
	from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.0/lib/active_support/dependencies.rb:136:in `require'
	from /usr/lib/ruby/gems/1.8/gems/og-0.28.0/lib/og.rb:14
	from /usr/local/lib/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
	from /usr/lib/ruby/gems/1.8/gems/activesupport-1.3.0/lib/active_support/dependencies.rb:136:in `require'
	from ./cookbook_dbconnect.rb:5
	from (irb):1NameError: undefined local variable or method `activerecord_connect' for #<Object:0xb7d66970>
	from (irb):14
ActiveRecord::ConnectionNotEstablished: ActiveRecord::ConnectionNotEstablished
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/connection_adapters/abstract/connection_specification.rb:225:in `retrieve_connection'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/connection_adapters/abstract/connection_specification.rb:78:in `connection'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:696:in `columns'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:1963:in `attributes_from_column_definition'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:1347:in `initialize_without_callbacks'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/callbacks.rb:236:in `initialize'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:406:in `create'
	from (irb):17
ActiveRecord::ConnectionNotEstablished: ActiveRecord::ConnectionNotEstablished
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/connection_adapters/abstract/connection_specification.rb:225:in `retrieve_connection'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/connection_adapters/abstract/connection_specification.rb:78:in `connection'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:696:in `columns'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:1963:in `attributes_from_column_definition'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:1347:in `initialize_without_callbacks'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/callbacks.rb:236:in `initialize'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:406:in `create'
	from (irb):19
ActiveRecord::ConnectionNotEstablished: ActiveRecord::ConnectionNotEstablished
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/connection_adapters/abstract/connection_specification.rb:225:in `retrieve_connection'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/connection_adapters/abstract/connection_specification.rb:78:in `connection'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:1299:in `quote_bound_value'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:1299:in `quote_bound_value'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:1283:in `replace_bind_variables'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:1283:in `replace_bind_variables'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:1272:in `sanitize_sql'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:1056:in `add_conditions!'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:1012:in `construct_finder_sql'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:924:in `find_every'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:918:in `find_initial'
	from /usr/lib/ruby/gems/1.8/gems/activerecord-1.14.0/lib/active_record/base.rb:380:in `find'
	from (irb):11:in `find_by_map'
	from (irb):21
class Criteria < Hash
  def initialize(values)
    values.each { |k,v| add(k, *v) }
    @or_criteria = nil
    @and_criteria = nil
  end
  :private
  attr_accessor :or_criteria, :and_criteria
  :public
  def add(field, value, operation='=')
    self[field] = [value, operation]
  end
  def or(criteria)
    c = self
    while c.or_criteria != nil
      break if c == criteria
      c = c.or_criteria 
    end
    c.or_criteria = criteria
    return self
  end
  def and(criteria)
    c = self
    while c.and_criteria != nil
      break if c == criteria
      c = c.and_criteria
    end
    c.and_criteria = criteria
    return self
  end   
class Criteria
  def to_where_clause
    sql = []
    values = []
    each do |field, value|
      if value.respond_to? :to_str
        value, operation = value, '='
      else
        value, operation = value[0..1]
      end
      sql << "#{field} #{operation} ?"
      values << value      
    end
    sql = '(' + sql.join(' AND ') + ')'
    if or_criteria
      or_where = or_criteria.to_where_clause
      sql = "(#{sql} OR #{or_where.shift})"
      values += or_where
    end
    if and_criteria
      and_where = and_criteria.to_where_clause
      sql = "(#{sql} AND #{and_where.shift})"
      values += and_where
    end
    return values.unshift(sql)
  end    
end
class ActiveRecord::Base 
  def self.find_by_criteria(id, criteria, args={}.freeze)
    args = args.dup
    args[:conditions] = criteria.to_where_clause
    find(id, args)
  end
end
review = Criteria.new(:title => ['%Review%', 'LIKE'])
bad_movie = Criteria.new(:title => ["%Movie%", 'LIKE'],
                         :content => 'Zero stars!')
good_game = Criteria.new(:title => ['%Game%', 'LIKE'],
                          :content => 'Four stars!')
no_cricket = Criteria.new(:title => ['%Cricket%', 'NOT LIKE'])
review.and(bad_movie.or(good_game)).and(no_cricket)
review.to_where_clause
["((title LIKE ?) AND
Error! (Exception?) Here's stdout:
BlogPost.find_by_criteria(:all, review).each { |post| puts post.title }
Game Review: Foosball Carnage
Movie Review: Foosball Carnage: The Movie
Just an idea...
Error! (Exception?) Here's stdout:
order_by = [[:title, 'ASC']]
...