"Joining" strings which may be nil (or) Handling Option hashes

G

Gavri Fernandez

Hi everyone,
I want to create a query based on options passed in through
an options hash to my method.

The query would look like
"title:ruby and author:dave and publisher:eek:reilly"
or
"title:ruby and publisher:addison-wesley"
or
"author:dave"
or some other combination based on what options have been set in the hash.

options[:author] |= "" followed by Array#join doesn't help me
because it would lead to successive 'and's in the query.

What is the Ruby Idiom to achieve what I want?

TIA
 
R

Robert Klemme

Gavri Fernandez said:
Hi everyone,
I want to create a query based on options passed in through
an options hash to my method.

The query would look like
"title:ruby and author:dave and publisher:eek:reilly"
or
"title:ruby and publisher:addison-wesley"
or
"author:dave"
or some other combination based on what options have been set in the hash.

options[:author] |= "" followed by Array#join doesn't help me
because it would lead to successive 'and's in the query.

What is the Ruby Idiom to achieve what I want?

Lots of, here are some:
opts = {"title"=>"ruby", "author"=>"dave", "publisher"=>"oreilly",
"foo"=>nil} => {"title"=>"ruby", "author"=>"dave", "foo"=>nil, "publisher"=>"oreilly"}
opts.select{|k,v|v} => [["title", "ruby"], ["author", "dave"], ["publisher", "oreilly"]]
opts.select{|k,v|v}.map{|k,v| "#{k}=#{v}"}.join(" and ")
=> "title=ruby and author=dave and publisher=oreilly"

Here's a more efficient variant - using #inject of course :)
=> "title=ruby and author=dave and publisher=oreilly"

Kind regards

robert
 
F

Francis Hwang

Lots of, here are some:
=> {"title"=>"ruby", "author"=>"dave", "foo"=>nil,
"publisher"=>"oreilly"}
opts.select{|k,v|v} => [["title", "ruby"], ["author", "dave"], ["publisher", "oreilly"]]
opts.select{|k,v|v}.map{|k,v| "#{k}=#{v}"}.join(" and ")
=> "title=ruby and author=dave and publisher=oreilly"

Here's a more efficient variant - using #inject of course :)
=> "title=ruby and author=dave and publisher=oreilly"

Maybe the OP isn't worried about this, but: Is there a way to represent
ORs as well as ANDs if you're doing this?

Francis Hwang
http://fhwang.net/
 
G

Gavri Fernandez

Lots of, here are some:
opts = {"title"=>"ruby", "author"=>"dave", "publisher"=>"oreilly",
"foo"=>nil} => {"title"=>"ruby", "author"=>"dave", "foo"=>nil, "publisher"=>"oreilly"}
opts.select{|k,v|v} => [["title", "ruby"], ["author", "dave"], ["publisher", "oreilly"]]
opts.select{|k,v|v}.map{|k,v| "#{k}=#{v}"}.join(" and ")
=> "title=ruby and author=dave and publisher=oreilly"

Here's a more efficient variant - using #inject of course :)
=> "title=ruby and author=dave and publisher=oreilly"


This is the solution I hit upon right after I sent my mail. Please
critique while I try to understand your solutions :)

def get_query(options)
query_fragments = []
options.each do |key, value|
query_fragments.push("#{key.id2name}:#{value}")
end
query = query_fragments.join(" and ")
end

Thanks Robert
 
R

Robert Klemme

Gavri Fernandez said:
Lots of, here are some:
opts = {"title"=>"ruby", "author"=>"dave", "publisher"=>"oreilly",
"foo"=>nil}
=> {"title"=>"ruby", "author"=>"dave", "foo"=>nil,
"publisher"=>"oreilly"}
opts.select{|k,v|v}
=> [["title", "ruby"], ["author", "dave"], ["publisher", "oreilly"]]
opts.select{|k,v|v}.map{|k,v| "#{k}=#{v}"}.join(" and ")
=> "title=ruby and author=dave and publisher=oreilly"

Here's a more efficient variant - using #inject of course :)
opts.inject(nil){|s,(k,v)| v ? (s ? s << " and " : "") << k << "=" <<
v :
s}
=> "title=ruby and author=dave and publisher=oreilly"


This is the solution I hit upon right after I sent my mail. Please
critique while I try to understand your solutions :)

def get_query(options)
query_fragments = []
options.each do |key, value|
query_fragments.push("#{key.id2name}:#{value}")
end
query = query_fragments.join(" and ")
end

I'm missing the condition. As far as I understood you you want to be able
to skip nil values. Did I get this wrong? Apart from that it does
certainly what you want. If you want to spare the intermediate array you
can do a bit optimization:

def get_query(options)
q = nil
options.each do |k,v|
q = (q ? q << " and " : "") << k << ":" << v
end
q
end

This is basically a verbose variant of my inject version.
Thanks Robert

You're welcome!

Kind regards

robert
 
R

Robert Klemme

Francis Hwang said:
Lots of, here are some:
opts = {"title"=>"ruby", "author"=>"dave", "publisher"=>"oreilly",
"foo"=>nil}
=> {"title"=>"ruby", "author"=>"dave", "foo"=>nil,
"publisher"=>"oreilly"}
opts.select{|k,v|v}
=> [["title", "ruby"], ["author", "dave"], ["publisher", "oreilly"]]
opts.select{|k,v|v}.map{|k,v| "#{k}=#{v}"}.join(" and ")
=> "title=ruby and author=dave and publisher=oreilly"

Here's a more efficient variant - using #inject of course :)
opts.inject(nil){|s,(k,v)| v ? (s ? s << " and " : "") << k << "=" << v
: s}
=> "title=ruby and author=dave and publisher=oreilly"

Maybe the OP isn't worried about this, but: Is there a way to represent
ORs as well as ANDs if you're doing this?

You could provide multiple values for a key and create an OR from that:

opts = {"title"=>["ruby","foobar"], "author"=>"dave"}

opts.inject(nil) do |s,(k,v)|
if v
(s ? s << " and " : "") <<
(Enumerable === v && ! (String === v) ?
v.inject(nil) {|s2,v2| (s2 ? s2 << " or " : "(") << k << ":" << v2} <<
")" :
"#{k}:#{v}")
else
s
end
end

Slightly unreadable... :)) You don't need the outer if-else-end though if
v is never nil.

Kind regards

robert
 
W

William James

Robert said:
Here's a more efficient variant - using #inject of course :)

=> "title=ruby and author=dave and publisher=oreilly"


I prefer this:

opts.to_a.map{|x| x.join('=') if x[1]}.compact.join(' and ')

----> "title=ruby and author=dave and publisher=oreilly"
 
G

Gavri Fernandez

I'm missing the condition. As far as I understood you you want to be able
to skip nil values. Did I get this wrong? Apart from that it does

My bad. My mail was cluttered with
1) The problem
2) Possible solution by handling unused options by not considering them.
3) Possible solution by setting unused options as nil and handling
them while "joining"

Anyway, thanks for the solutions.
 
W

William James

William said:
Robert said:
Here's a more efficient variant - using #inject of course :)
"="
<< v :
=> "title=ruby and author=dave and publisher=oreilly"


I prefer this:

opts.to_a.map{|x| x.join('=') if x[1]}.compact.join(' and ')

----> "title=ruby and author=dave and publisher=oreilly"

Too prolix. Better:

opts.map{|x| x.join('=') if x[1]}.compact.join(' and ')

Here are the stages through which the data travels:

{"title"=>"ruby", "author"=>"dave", "foo"=>nil, "publisher"=>"oreilly"}
["title=ruby", "author=dave", nil, "publisher=oreilly"]
["title=ruby", "author=dave", "publisher=oreilly"]
"title=ruby and author=dave and publisher=oreilly"
 

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

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,053
Latest member
BrodieSola

Latest Threads

Top