elz: (shh)
elz ([personal profile] elz) wrote in [community profile] rubyonrails2009-04-21 02:27 pm

Blocks for beginners

Blocks are tricky to wrap your brain around and hard to explain, but they're very, very useful, so you'll probably have to tackle them sooner or later.



When it comes down to it, a block is just a bunch of code found within a pair of curly braces {} or the words 'do' and 'end'. The block itself is a bit like a sentence fragment - it's not an independent object, and it can't stand on its own. If you open up irb and type {|x| puts x} on a line, irb will just say, "dude, I think there's something wrong with your hash." What's nifty about blocks isn't so much what they are as how they can be used.

Basic blocks



First, let's see some of your most common, garden-variety Ruby blocks.

Take an array of companions from Doctor Who:

companions = ['Rose', 'Martha', 'Donna']

Let's say they're in mortal peril (which happens pretty often, actually) and the Doctor is shouting their names, so we want to make each name uppercase and add an exclamation point to it:

companions.each {|companion| puts companion.upcase + '!'}

This will give you:

ROSE!
MARTHA!
DONNA!


companions is an array; each is an array method that takes a block. companion is just a local variable name; we could have used x or human or raxicoricofallapatorius there and have gotten the same result. The pipes (|wheee|)just give you a place to name your variable(s). What the each method actually does is loop through your array and run the code in your block on each element in turn until it gets to the end.

It would work the same way if you did it like this:

companions.each do |companion|
  puts companion.upcase + '!'
end


The do/end style is usually handy if you've got more than one line of code in there.

And to make sure beginners are as confused as they can possibly be, you can also write the same code this way:

for companion in companions
  puts companion.upcase + '!'
end


each is a bit like the Doctor himself that way - it comes in a lot of different guises, but they're all the same alien on the inside.

Note: if you're using these methods inside a Rails view, which is pretty common, you wouldn't use 'puts', you'd just do:

<%- companions.each do |companion| -%> 
  <%= companion.upcase + '!' %>
<%- end -%>


The erb tags (<%= %>) take care of outputting the results for you. Of course, you'd probably want to add some html to that too. And this is a case where you would want to use one of the multi-line methods instead of companions.each {|companion| companion.upcase + '!'} because the one-line block will return the original array, while the multi-line methods let you output each iteration.

Another very common array method that takes a block is collect (also known as map - it doesn't matter which name you use, it's the exact same method). Let's say you want to take your array of names and make a new array of those same names being shouted:

companions_in_danger = companions.collect {|companion| companion.upcase + '!'}

Now in addition to our original companions array, ['Rose', 'Martha', 'Donna'], we have a second array called companions_in_danger: ['ROSE!', 'MARTHA!', 'DONNA!'].

You can also use do/end here:

companions_in_danger = companions.collect do |companion|
  companion.upcase + '!'
end


But for companion in companions only works for each.

Note #2: you *can* actually stick <%= companions.collect {|companion| companion.upcase + '!'} %> in a Rails view, although you'll want to make sure to add spaces, line breaks or other html. It works because the collect method returns the new array and not the original and because erb automatically converts arrays into strings, although obviously that works out best if what you have *is* an array of strings.

If Companion were a class and each companion had a name that you could access via companion.name, it might be very handy to do something like:

companion_names = companions.collect {|companion| companion.name}


In fact, it's so handy, there's a shorthand for it in Rails (and coming soon/now, in Ruby 1.9):

companion_names = companions.collect(&:name)


If you're just starting out, it may be easiest to think of that as magical shorthand that turns a symbol into a block; that's not quite accurate, but we'll get to that if/when we get to procs. If you're curious, try googling "symbol to proc".

How blocks actually work



The key is inside the method definition. This is the simplest possible use of a block (so simple it's totally pointless):

def hollow_method
  yield
end

hollow_method {puts "wakka wakka"}


would give you the exact same results as:

puts "wakka wakka" on its own. You'll notice that yield is the magic word - yield is what says "this method should have a block and this is where you should run its code."

Try using more than one yield:

def double_your_fun
  yield
  yield
end

double_your_fun {puts "wakka"}


If you think that will output:

wakka
wakka


you'd be right.

Things get more interesting when you start sending information to and from the block via variables:

def storytime
  print "One day, the Doctor and "
  companion = "Donna"
  yield(companion)
  print " were out for a walk on an alien planet."
end

storytime {|name| print name }


Will give you:
"One day, the Doctor and Donna were out for a walk on an alien planet."

But if you decide you don't like that story, you could change things up a bit:

storytime do |name|
  name = "Martha"
  print name
end


That will give you:
"One day, the Doctor and Martha were out for a walk on an alien planet."

When I hit yield(companion) in storytime, I run the code in the block and I send it the value of companion, which gets picked up by the local variable. It doesn't matter that I've named it something else. When the block finishes, the companion is still Donna, though. I've only succeeded in bringing Martha in as a guest star, unless I change the method to set the value of companion to whatever yield(companion) returns.

Now, those are things you could do just as easily with a storytime(name) method. How do methods like each and collect actually work? Might look something like this (total supposition, but they will actually run):

class Array  
  def fake_each
    i = 0
    while i < self.length do
      yield(self[i])
      i = i + 1
    end
  end  

  def fake_collect
    new_array = []
    i = 0
    while i < self.length do
      new_array << yield(self[i])
      i = i + 1
    end
    new_array    
  end
end


If I have three things in my array, I wind up calling yield three times, and each time, I send a different value to the block. I can also do things with the values I get back from the block, like add them to a new array. You're able to make your methods so much more flexible this way, and reuse a lot more code.

(I've used each and collect as examples here, but block behavior is the same for methods like select, find, sort, etc. and any other methods with blocks.)

Next up: procs and lambda! At least, as much about them as I understand myself. *cough*

haAAAyEJAInl

(Anonymous) 2013-06-08 04:24 pm (UTC)(link)
nice list.I want to learn a new programming laugnage but i dunno which to choose. I dont wanna learn one and by the time im finished its outdatedI want to learn one thats not gonna go anywhere for ten years or one that is new but is turning heads and will be the next big thing 2 years down the road. Just like what google maps did for ajax.currently im leaning towards php as it so beautifully written and many huge companies use it (wordpress, joomla etc)