views:

85

answers:

5

I find myself using PHP-like loops a lot in Ruby and it feels wrong when the rest of the language is so neat. I wind up with code like this:

conditions_string = ''

zips.each_with_index do |zip, i|

 conditions_string << ' OR ' if i > 0
 conditions_string << "npa = ?"

end

# Now I can do something with conditions string

I feel like I should be able to do something like this

conditions_string = zips.each_with_index do |zip, i|

 << ' OR ' if i > 0
 << "npa = ?"

end

Is there a 'Neat' way to set a variable with a block in Ruby?

+1  A: 

You don't seem to be accessing zip in your loop, so the following should work:

conditions_string = (['npa = ?'] * zips.length).join(' OR ')

If you need access to zip, then you could use:

conditions_string = zips.collect {|zip| 'npa = ?'}.join(' OR ')
Phil Ross
Your first solution made the most sense in my application, where I needed to preserve the zips array, but create a string based on the length of it. This was for a MySQL query by the way
Bloudermilk
+4  A: 

The first thing I thought of was this:

a = %w{array of strings}             => ["array", "of", "strings"]
a.inject { |m,s| m + ' OR ' + s }    => "array OR of OR strings"

But that can be done with just

a.join ' OR '

And while I think you will need that construct soon, to duplicate your exact example I might just use:

([' npa = ? '] * a.size).join 'OR'
DigitalRoss
Cool, I didn't remember the version without argument but with block worked this way.
Benno
Is there an advantage to this solution over the one suggested by Phil Ross? His fits into my application better, but if there's a large performance overhead, I can use this too.
Bloudermilk
No, my first try with inject was fun but too complex and not what you immediately needed. I think my last line is the one you want, just because it's marginally simpler. We get so used to solving problems with blocks (everyone, including me, at first) that we might overlook a problem simple enough to just require `*` and `#join`. :-)
DigitalRoss
+3  A: 

Since you don't actually use the value of zip, I'd suggest

zips.map {|zip| "npa = ?" }.join(" OR ")

but in general I'd suggest looking at the Enumerable#inject function to avoid this kind of loops.

Benno
A: 

In 1.8.7+ you can use each_with_object

It replaces DigitalRoss's 'inject' idiom with this:

a = %w{hello my friend}  => ["hello", "my", "friend"]
a.each_with_object("") { |v, o| o << v << " NOT " }  => "hello NOT my NOT friend NOT"
banister
+1  A: 

Although others have given more idiomatic solutions to your specific problem, there's actually a cool method Object#instance_eval, which is a standard trick that many Ruby DSLs use. It sets self to the receiver of instance_eval inside its block:

Short example:

x = ''
x.instance_eval do
    for word in %w(this is a list of words)
     self << word  # This means ``x << word''
    end
end
p x
# => "thisisalistofwords"

It doesn't pervasively cover everything in the way Perl's $_ does, but it allows you to implicitly send methods to one single object.

jleedev