tags:

views:

253

answers:

4

Is it possible to define a block in an inline statement with ruby? Something like this:

tasks.collect(&:title).to_block{|arr| "#{arr.slice(0, arr.length - 1).join(", ")} and #{arr.last}" }

Instead of this:

titles = tasks.collect(&:title)
"#{titles.slice(0, titles.length - 1).join(", ")} and #{titles.last}"

If you said tasks.collect(&:title).slice(0, this.length-1) how can you make 'this' refer to the full array that was passed to slice()?

Basically I'm just looking for a way to pass the object returned from one statement into another one, not necessarily iterating over it.

+1  A: 

I'm not sure exactly what you're trying to do, but:

If you said tasks.collect(&:title).slice(0, this.length-1) how can you make 'this' refer to the full array that was passed to slice()?

Use a negative number:

tasks.collect(&:title)[0..-2]

Also, in:

"#{titles.slice(0, titles.length - 1).join(", ")} and #{titles.last}"

you've got something weird going on with your quotes, I think.

Thanks, good to know about the negative number.
bwizzy
+1  A: 

I don't really understand why you would want to, but you could add a function to the ruby classes that takes a block, and passes itself as a parameter...

class Object
  def to_block
    yield self
  end
end

At this point you would be able to call:

tasks.collect(&:title).to_block{|it| it.slice(0, it.length-1)}

Of course, modifying the Object class should not be taken lightly as there can be serious consequences when combining with other libraries.

krdluzni
is there any way to do the block, yield self inline?
bwizzy
krdluzni
+2  A: 

You're kind of confusing passing a return value to a method/function and calling a method on the returned value. The way to do what you described is this:

lambda {|arr| "#{arr.slice(0, arr.length - 1).join(", ")} and #{arr.last}"}.call(tasks.collect(&:title))

If you want to do it the way you were attempting, the closest match is instance_eval, which lets you run a block within the context of an object. So that would be:

tasks.collect(&:title).instance_eval {"#{slice(0, length - 1).join(", ")} and #{last}"}

However, I would not do either of those, as it's longer and less readable than the alternative.

Chuck
A: 

Although there are many good answers here, perhaps you're looking for something more like this in terms of an objective:

class Array
  def andjoin(separator = ', ', word = ' and ')
    case (length)
    when 0
      ''
    when 1
      last.to_s
    when 2
      join(word)
    else
      slice(0, length - 1).join(separator) + word + last.to_s
    end
  end
end

puts %w[ think feel enjoy ].andjoin # => "think, feel and enjoy"
puts %w[ mitchell webb ].andjoin # => "mitchell and webb"
puts %w[ yes ].andjoin # => "yes"

puts %w[ happy fun monkeypatch ].andjoin(', ', ', and ') # => "happy, fun, and monkeypatch"
tadman