[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

DSL help?

Ezra Zygmuntowicz

1/3/2006 6:55:00 PM

Hey rubyists-

I was wondering if someone could help me make this small dsl I wrote
into a little bit better syntax. The Cond class just takes a block in
its constructor and converts what's inside the block into an sql
where clause with ? syntax like this:

c = Cond.new do
first_name ‘=’, ‘Ezra’
start_date ‘between’, ‘2006-01-01', ‘2006-01-30'
last_name ‘like’, ‘Zyg%’
sql ‘hosts.id = something.id'
end

p c.where
#=> ["first_name = ? and start_date between ? and ? and last_name
LIKE ? and hosts.id = something.id", "Ezra", "2006-01-01",
"2006-01-30", "Zyg%"]

I would like to be able to get rid of the quotes around the
operators '=', '<=', 'LIKE' and so on to become =, <= and LIKE . Also
I would like to be able to get rid of the need for commas inside the
block as well. Inside of the Cond class initialize method it justs
uses instance_eval &block to get the block contents and then uses
method_missing to build a nested array for each statement.

Anyone have any better ideas to offer on how to make this interface
a little nicer? Thanks in advance.


class Cond
# Uses a block to initialize the condition:
# c = InVisible::Cond.new do
# month '<=', 11
# year '=', 2005
# name 'LIKE', 'ruby%'
# end
#
# c.where -> ["month <= ? and year = ? and name LIKE ?", 11,
2005, "ruby%"]
#
# to include direct SQL, use like this:
# c = InVisible::Cond.new do
# sql "hosts.id = logs.host_id and hosts.name", 'like',
"123.23.45.67"
# end
# if a value needs to by typed (f.e. in Postgres: "ip <
inet ?"), use a form of:
# c = InVisible::Cond.new do
# ip '= inet', '123.34.56.78/24'
# end
#
# to expand an existing condition, use the << method
# c << ['age', '>', 30]

def initialize(&block)
@args = []
instance_eval(&block) if block_given?
end

def method_missing(sym, *args)
@args << [sym,args.flatten].flatten
end

def <<(*args)
@args << [args.flatten].flatten
end

def where(args=@args)
q = []
ary = []
args.each do |pair|
iv = pair[1..99]
unless iv.last.nil? || iv.last.to_s == ''
if pair[0].to_s =~ /^sql.*/ then
pair[0] = iv.shift
end
case iv.size
when 0:
q << "#{pair[0]}" # the case when there is
only one (sql) statements
when 1:
q << "#{pair[0]} = ?"
ary << iv.last
when 2:
operator = iv[0]
q << "#{pair[0]} #{operator} ?"
ary << iv.last
when 3:
op = case iv[0]
when 'between': "between ? and ?"
end
q << "#{pair[0]} #{op}"
ary << iv[-2] << iv[-1]
end
end
end
return [q.join(" and ")].concat(ary)
end

end


Cheers-
-Ezra




4 Answers

gabriele renzi

1/3/2006 7:22:00 PM

0

Ezra Zygmuntowicz ha scritto:
> Hey rubyists-
>
> I was wondering if someone could help me make this small dsl I
> wrote into a little bit better syntax. The Cond class just takes a
> block in its constructor and converts what's inside the block into an
> sql where clause with ? syntax like this:
>
> c = Cond.new do
> first_name â??=â??, â??Ezraâ??
> start_date â??betweenâ??, â??2006-01-01â?², â??2006-01-30â?²
> last_name â??likeâ??, â??Zyg%â??
> sql â??hosts.id = something.id'
> end
>
> p c.where
> #=> ["first_name = ? and start_date between ? and ? and last_name LIKE
> ? and hosts.id = something.id", "Ezra", "2006-01-01", "2006-01-30",
> "Zyg%"]
>
> I would like to be able to get rid of the quotes around the
> operators '=', '<=', 'LIKE' and so on to become =, <= and LIKE . Also I
> would like to be able to get rid of the need for commas inside the
> block as well. Inside of the Cond class initialize method it justs uses
> instance_eval &block to get the block contents and then uses
> method_missing to build a nested array for each statement.

look on RAA for the "Criteria" package, you may find it interesting and
a moe tested than your own. At least you can fish in it for good ideas
if you don't want to use it :)

rcoder@gmail.com

1/3/2006 8:10:00 PM

0

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

Ezra Zygmuntowicz

1/3/2006 8:44:00 PM

0


On Jan 3, 2006, at 12:12 PM, rcoder wrote:

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

Thanks rcoder-

That will definitely help me move forward. I appreciate it.


Thanks-
-Ezra




Francis Hwang

1/3/2006 9:52:00 PM

0

gabriele renzi wrote:
> look on RAA for the "Criteria" package, you may find it interesting and
> a moe tested than your own. At least you can fish in it for good ideas
> if you don't want to use it :)

Or Lafcadio ( http://rubyforge.org/projects... ) which
unabashedly stole the idea from Criteria and altered it slightly. Maybe
improved it? Who's to say.

For example:

users = User.get { |u| u.fname.equals( 'Francis' ) & u.lname.equals(
'Hwang' ) }