Below is just my 20-minute version -- consider it a source of ideas for
doing a more complete DSL, not a real library.
With it, you can do something like the following:
q = Query.new do
foo == 'bar' => exact value
baz <=> (1..100) => 'between'
woo =~ 'substri%' => 'like'
fiz < 10 => lt, gt, leq, geq, etc., should all "just work"
end
q.to_sql =>
["foo = ? AND baz BETWEEN ? AND ? AND fiz < ?", ["bar", 1, 100, 10]]
(#to_sql returns query and array of bind params)
# query.rb
class Clause
attr_reader :name, :test, :value
def initialize(name)
@name = name
end
def ==(other)
@test = :equals
@value = other
end
def =~(pattern)
@test = :like
@value = pattern
end
def <=>(range)
@test = :between
@value = range
end
def to_sql
case @test
when :equals
["#{@name} = ?", @value]
when :like
["#{@name} LIKE ?", @value]
when :between
["#{@name} BETWEEN ? AND ?", [@value.begin, @value.end]]
else
["#{@name} #{@test} ?", @value]
end
end
def method_missing(name, *args)
@test = name
@value = args.first
end
end
class Query
attr_reader :vars
def initialize(&block)
@vars = []
instance_eval(&block)
end
def method_missing(name, *args)
puts "Query#method_missing(#{([name]+args).join(', ')})" if $DEBUG
cv = Clause.new(name)
@vars << cv
cv
end
def to_sql(bool='AND')
params = []
query = []
@vars.each do |cv|
q,p = cv.to_sql
query << q
params << p
end
[query.join(" #{bool} "), params.flatten]
end
end