elz: (shh)
[personal profile] elz posting in [community profile] rubyonrails
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*

(no subject)

Date: 2009-05-26 01:02 am (UTC)
tassosss: Shen Wei Zhao Yunlan Era (Default)
From: [personal profile] tassosss
Okay. I think I have this but I just want to clarify how the |variable| works.
The block is this (ps I love your examples!)

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

What happens is that the .each takes the first name in the array and sends it to the block where the block says "you're my input and I'm going to now call you 'companion'." Then the block says "since you are a 'companion' I'm going to output you in all caps with an exclamation point." Now the block's job is done and the .each sends it the next item in the array.

Essentially the |companion| is only relevant for the block itself and once the array item leaves the block it's no longer associated with the 'companion' variable. Is that right?

ALZMkSPHuNsKmbS

Date: 2013-05-28 05:17 am (UTC)
From: (Anonymous)
I have a feeling that I am going to be miargting elsewhere and deleting my LiveJournal. I've stuck with LJ through a lot but this last move shows a truly epic level of bad judgement, and even if they fix the privacy issue of crossposted comments, I fear they will do the something similarly boneheaded in the future... and I don't want any of my old friendslocked entries to become fair game while I'm not looking.Rather sad about it.

haAAAyEJAInl

Date: 2013-06-08 04:24 pm (UTC)
From: (Anonymous)
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)

MZQnSPFGxaa

Date: 2013-05-26 11:44 am (UTC)
From: (Anonymous)
Oh my gosh my darling feirnd! you are tooo toooo too much. Thank you for the lovely and perfect baby dress for little L. I am in love with it times a zillion. What a perfect treat to greet me when I returned home from a LOOOONG monday at work!thank you thank you thank you!you are too kind.sg

WpFOJWILdbeIVKevoha

Date: 2013-06-09 01:52 am (UTC)
From: (Anonymous)
Hi Patrick,First of all thanks for pitutng down an article for Kaltura which is rare. But am facing a difficulty which you might be able to understand and help me out.I took the trial version of Kaltura KMC and tried using the gem Kaltura to integrate a video from KMC into my rails app by using this in my view:[544,360] %>but am not getting the video to be rendered at my app, am just getting a string-containing div and some JavaScript code containing variables for the video.Have defined the credentials in yml file for kaltura as per your github site kaltura_fu.

yemNBgoagrgzS

Date: 2013-06-11 03:41 pm (UTC)
From: (Anonymous)
2011年5月17日 at 1:00 AM I see. So you already know where to stay in LA, like your fnierd's house? I guess you can find some houses for short time.You may have to pay a rent for a month, but you have your own space. Or is it like you are looking for some home-stay type of house?If you need any help, just let me know Anyway, come to visit me when you get here. My house is in downtown. You know my email and cell phone, right?

syyhncXLZL

Date: 2013-05-28 05:58 am (UTC)
From: (Anonymous)
Feliz Navidad to you dear friend you named two thngis that have been passing through my head in the last few days love and beauty I really rely on them to get through each day thank you for being who you are and I hope the new year has more soft spots and beautiful vistas to look over past experiences handled courageously! xx

July 2010

S M T W T F S
    123
4567 8910
11121314151617
18192021222324
25262728293031

Page Summary

Style Credit

Expand Cut Tags

No cut tags