0

I'm newish to ruby, used to JS and C#, dealing with nested block and i have 2 'loops' here which should print the exact same thing.

    page.search( "//div[@id='mw-content-text']" ).search("p").find do |p| 
        puts p.inner_text.gsub(/[^a-z ]/i, '').split( ' ' )
    end

    page.search( "//div[@id='mw-content-text']" ).search("p").find do |p| 
    p.inner_text.gsub(/[^a-z ]/i, '').split( ' ' ).each do |word|
        puts word
        end
    end

They both start by getting the all the paragraph tags in a page, then iterating through them. The first acts as expected, but when i try to iterate through each word with a nested block, i then only get one result from the outer block. it's as if the first 'end' is breaking the outer block or something. Is this normal ruby behaviour? have i missed something obvious?

Thanks for your help.

Simon.

1
  • Can you paste the exception message Commented Jan 10, 2014 at 9:51

3 Answers 3

1

If you want to loop over all the p elements, you probably want to use each and not find. The find method "passes each entry in enum to block. Returns the first for which block is not false."

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks Matt, this did the trick, and with Timo's explanation I can really see what was going wrong. Cheers.
1

Matt is correct. Use each. However, considering your novice status, I think you might benefit from a bit more detailed explanation of what is going on there.

In Ruby every method call or block returns what ever the last executed line of code in it returned (or nil if it's empty). You do not need to call return (and for blocks you can't call it anyway) explicitly unless you wish to prematurely stop the execution. Now keeping that in mind we can test that puts("something").nil? #=> true. In conditional statements nil is considered to be false, which is why the first call for find runs through the entire set. For every p tag you just call puts which returns nil and tells find that this is not the element we are looking for. However, the each method returns whatever it was called for (so you can chain calls) as illustrated by [].each {}.class #=> Array, thus indicating to the find method that whatever you were looking for was found and we can stop iterating the set. And finally the find method then returns the first p element on the search results.

2 Comments

Thanks for the explanation Timo, i did have to read it twice, but i understand what's happening now. Ruby seems to be a very different animal to languages i have used before. ps. Don't have the rep to vote up your answer. Cheers
It is very different beast in deed. I suggest that when ever your in doubt of what some method does check apidock.com and/or experiment in the rails console or plain old irb.
1
page.search( "//div[@id='mw-content-text']" ).search("p") 

gain Enumerable.

Enumerable#find

Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns its result when it is specified, or returns nil otherwise. Example.

(1..10).find {|item| p item}
# 1.

you can use Enumerable#find_all, Enumerable#collect, map

Example.

(1..10).find_all {|item| p item}
# 1 2 3 4 ... 10
[1,2,3,4,5,6,7,8,9,10]

Hope that it can help you.

2 Comments

Hey Chuang, thanks for the answer, i've got it working using .each, but it's good to know there is another approach.
Your find_all example may be a little bit misguiding. It's purpose is to filter an array to contain only the elements which in fact do contain the objects for which the block returns something non nil and non false (as oppose to find that returns the first found, it returns all found and consequentially has to go through the entire set, at least for your usual arrays and such). Map and collect which are synonyms are for mapping data to something else, typically used e.g. to reduce an array of objects to an array of some attribute of those objects. Use each for just iterating through a set.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.