# DSL help?

**URL:** <https://www.ruby-forum.com/t/dsl-help/89015>\
**Category:** Ruby\
**Created:** [January 3, 2006, 7:56pm UTC](https://www.ruby-forum.com/t/dsl-help/89015 "2006-01-03T19:56:35Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![ezra\_z](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/e/f07891/32.png) [@ezra\_z](https://www.ruby-forum.com/u/ezra_z)\
**Post date:** [January 3, 2006, 7:56pm UTC](https://www.ruby-forum.com/t/dsl-help/89015/1 "2006-01-03T19:56:35Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![ezra\_z](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/e/f07891/32.png) [@ezra\_z](https://www.ruby-forum.com/u/ezra_z)\
**Post date:** [January 3, 2006, 8:24pm UTC](https://www.ruby-forum.com/t/dsl-help/89015/2 "2006-01-03T20:24:18Z")

</div>

Ezra Z. ha scritto:

> last\_name â??likeâ??, â??Zyg%â??  
> 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 🙂

---

<div class="post-metadata">

**Author:** ![ezra\_z](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/e/f07891/32.png) [@ezra\_z](https://www.ruby-forum.com/u/ezra_z)\
**Post date:** [January 3, 2006, 9:15pm UTC](https://www.ruby-forum.com/t/dsl-help/89015/3 "2006-01-03T21:15:48Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![ezra\_z](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/e/f07891/32.png) [@ezra\_z](https://www.ruby-forum.com/u/ezra_z)\
**Post date:** [January 3, 2006, 9:50pm UTC](https://www.ruby-forum.com/t/dsl-help/89015/4 "2006-01-03T21:50:20Z")

</div>

On Jan 3, 2006, at 11:22 AM, gabriele renzi wrote:

> > sql â??hosts.id = something.id’  
> > 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 🙂

Gabriele-

```
Ahh thanks for that I will give it a look.

```

Cheers-  
-Ezra

---

<div class="post-metadata">

**Author:** ![ezra\_z](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/e/f07891/32.png) [@ezra\_z](https://www.ruby-forum.com/u/ezra_z)\
**Post date:** [January 3, 2006, 9:46pm UTC](https://www.ruby-forum.com/t/dsl-help/89015/5 "2006-01-03T21:46:49Z")

</div>

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

> 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

---

<div class="post-metadata">

**Author:** ![ezra\_z](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/e/f07891/32.png) [@ezra\_z](https://www.ruby-forum.com/u/ezra_z)\
**Post date:** [January 3, 2006, 10:53pm UTC](https://www.ruby-forum.com/t/dsl-help/89015/6 "2006-01-03T22:53:46Z")

</div>

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/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’ ) }
