DSL help?

  • Thread starter Ezra Zygmuntowicz
  • Start date
E

Ezra Zygmuntowicz

Hey rubyists-

I was wondering if someone could help me make this small dsl I =
wrote =20
into a little bit better syntax. The Cond class just takes a block in =20=

its constructor and converts what's inside the block into an sql =20
where clause with ? syntax like this:

c =3D Cond.new do
first_name =E2=80=98=3D=E2=80=99, =E2=80=98Ezra=E2=80=99
start_date =E2=80=98between=E2=80=99, =E2=80=982006-01-01=E2=80=B2, =
=E2=80=982006-01-30=E2=80=B2
last_name =E2=80=98like=E2=80=99, =E2=80=98Zyg%=E2=80=99
sql =E2=80=98hosts.id =3D something.id'
end

p c.where
#=3D> ["first_name =3D ? and start_date between ? and ? and last_name =20=

LIKE ? and hosts.id =3D something.id", "Ezra", "2006-01-01", =20
"2006-01-30", "Zyg%"]

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

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


class Cond
# Uses a block to initialize the condition:
# c =3D InVisible::Cond.new do
# month '<=3D', 11
# year '=3D', 2005
# name 'LIKE', 'ruby%'
# end
#
# c.where -> ["month <=3D ? and year =3D ? and name LIKE ?", 11, =20=

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

def initialize(&block)
@args =3D []
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=3D@args)
q =3D []
ary =3D []
args.each do |pair|
iv =3D pair[1..99]
unless iv.last.nil? || iv.last.to_s =3D=3D ''
if pair[0].to_s =3D~ /^sql.*/ then
pair[0] =3D iv.shift
end
case iv.size
when 0:
q << "#{pair[0]}" # the case when there is =20
only one (sql) statements
when 1:
q << "#{pair[0]} =3D ?"
ary << iv.last
when 2:
operator =3D iv[0]
q << "#{pair[0]} #{operator} ?"
ary << iv.last
when 3:
op =3D 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
 
G

gabriele renzi

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 :)
 
R

rcoder

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
 
E

Ezra Zygmuntowicz

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
 
F

Francis Hwang

gabriele said:
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/lafcadio/ ) 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' ) }
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

basic ENCAPSULATION help 3
Sane #hash implementation? 17
anyone seen this before? (long) 1
[ANN] EventLoop 0.0.20050825.1600 31
C DSL anyone? 13
FXRuby 1.6.4: what's the trick? 1
Code safety question 2
Help with code 0

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,062
Latest member
OrderKetozenseACV

Latest Threads

Top