tags:

views:

68

answers:

5

Hello, I'm learning Ruby (using the Pickaxe book) and I've encountered a little confusion with a block operation that goes a little like this:

class CsvReader
  def initialize
    @book_in_stock = []
  end

  def read_in_csv_data(csv_file_name)
    CSV.foreach(csv_file_name, headers: true) do |row| # 1.
      @book_in_stock << BookinStock.new(row["ISBN"], row["Amount"]) # 2.
    end
  end
end

My confusion exists on the commented "#1" and "#2", I don't understand how we get "123" and "456" from a csv_file_name of "test.csv" containing the data:

"ISBN","Amount"
"123","456"

How is it that row["ISBN"] knows that the next row corresponds to it? Do all blocks behave this way? What are they really called? Could someone explain them a little better?

Thanks.

A: 

THis is a feature of the built-in csv module that comes with ruby. When you create the CSV object you pass in the headers: true flag which tells tells the csv object the names or the columns as well as how many columns exist. After that the foreach method defined on the cvs class sets up the alias row for each row in the table. the hash row is then populated with the data in each row.

ennuikiller
I tried to understand and I think I did, but you aren't being very coherent. Thanks!
asdf
it was written hastily, so I edited it, hopefully it makes more sense.
ennuikiller
+1  A: 

Haven't personally used Ruby's CSV class, but it looks like since it's being told that "headers:true" its assuming that the first line contains headers and each following line is data corresponding to the header above it.

Austin Fitzpatrick
A: 

Ok, so whenever you create a block using do...end or {...} you are effectively creating an anonymous method:

my_method do |arg1,arg2|
  ...
end

is the same kind of thing as

def ... arg1, arg2
  ...
end

The point is, the method you are calling (foreach) can call the code code inside your do...end block whenever it wants, and with whichever arguments it chooses. For the foreach method, it will call your block once for every row it has to deal with, and the row will be available inside the row variable (differently each time) just as if it was a method argument

EDIT

The answer to your specific question lies in the type of variable which is passed into the row. It looks like it's a CSV::Row object, which defines a method row.[] that allows you to get the value for that row which appears under the header you pass in

Gareth
Seems complicated, but I guess so far this is the most correct answer. Thanks
asdf
So, is it a hash or is it a method defined as "row.[]" :)
asdf
To be honest, the difference doesn't matter as far as you're concerned. "Hash-style" access via [] is a common idiom among a lot of ruby classes which act as collections. If you need to, you can always call output row.class somewhere and see what that returns...
Gareth
+2  A: 

Blocks are a ruby idiom for describing a closure, or a block of code that has its own scope (it doesnt need to know about other parts of the code). Blocks have two styles, if the code goes on more than one line, you use the do syntax (given a data structure or some enumerable named @entries)

@entries.each do |entry|
  #do something
end

if it is on one line you can simplify the block as such

@entries.each{|entry| execute something here }

explicitly, what either code block does is pass in one entry at a time, assigned to entry and do an operation over it. It is like an anonymous inner function.

They are a little hard to understand at first, but really useful and all over the ruby world. We have blockitis sometimes.

As a use case, consider that we want to do something to each value in an array.

[1,2,3].map{|item| item+1} #=> takes each item in the array gets one added to it

what isn't so obvious is that this function returns an array [2,3,4] because the inner function takes care of the iteration over the data structure. This is much more convenient and declarative than having another function that operates over the array and another one yet to add one to each item.

in the csv example you are taking each row and adding it to a data structure called @books_in_stock but its not as explicit, I think as the example I gave. Check out the Enumearble class http://apidock.com/ruby/Enumerable for many good examples of using blocks.

Jed Schneider
This is all very interesting, and I have learned much just from it, but it doesn't truly answer the question in question I don't think -- thanks!
asdf
A: 

the row coming in is treated as a hash so row["ISBN"] is calling the ISBN key which is set the first time through the block because you set the headers to true as one of the CSV foreach options. so the key value for ISBN is 123. Ruby syntax looks like "isbn" => "123" or key => value

in this case each row coming in is treated as a separate hash where the key is set by the header row and the value is derived from the placement within the csv row.

Jed Schneider