Topic: iterating through collection in view

OK, total ruby/rails newbie here.  I've been banging my head against this for a
number of hours now, and am sure I'm doing something stupid.

I've got two classes, modeled loosely on the (great) tutorials here on this forum.

class SafeSection < ActiveRecord::Base
    belongs_to :safe_verifications

    # adds up and returns the total dollar value of the section.
    def total
        change = pennies + nickels + dimes + quarters + other_coin
        return change
    end
end

class SafeVerification < ActiveRecord::Base
    has_many :safe_sections
end


In the view I can add multiple section objs associated with a particular parent (Yay!).  The problem lies in displaying some info.

Created in the controller:

 def create
  @safe_verification = SafeVerification.new(params[:safe_verification])
  params[:safe_sections].each_value { | safe_section | @safe_verification.
safe_sections.build(safe_section) }
...

My problem in the view:
<% for safe_verification in @safe_verifications %>
  <tr>
        <td> <%= h safe_verification.login %> </td>
        <td> <%= number_to_currency safe_verification.over_short %> </td>
        <td>  <%= safe_verification.safe_sections.count %>
        <%=  safe_verification.safe_sections[0].total unless safe_verification
.safe_sections[0].nil?  %>
        <%=  safe_verification.safe_sections.each { |a| a.total unless a.nil? }  %>
        </td>
<% end %>

I'm able to access the model safe_verification.  I can access the safe_sections collection and use the count member (line 5).  I can even access the collection by the array access(line 6).  But the .each line (8) just doesn't work.  I get #<SafeSection:0xb72ee96c> for each of the outputs, instead of the output of the total function, as I was intending.

What am I doing wrong?  It seems the SafeSection address is really close, but I want to dereference it, but don't know how.  google isn't enough in this case.

Thanks to anyone who can shed some light on this.

Re: iterating through collection in view

The "each" method just calls the block for each element in the array - it doesn't do anything special with what is returned from the block - it doesn't even care about what is happening in the block.

It looks like you might want the inject method:

<%= safe_verification.safe_sections.inject { |sum, a| sum + a.total } %>

Still I'm not certain that will work as expected. You may want to play around with this in script/console. Start that up by running this command.

ruby script/console

You can fetch models here and test looping through them. The result of the call will be output so you can see what everything does.

Railscasts - Free Ruby on Rails Screencasts

Re: iterating through collection in view

Thanks for the quick reply Ryan,

I'm trying that suggestion now.  Just some info for anyone on Ubuntu:
If you try running the ./script/console and you see an error about readline missing, do an "apt-get install libreadline5-dev" and then recompile the ruby source.  Make sure you use the --with-readline-dir=/usr/lib switch to ./configure.  Otherwise it doesn't seem to pick up the location of the readline automatically.

The console script works now, and I'm off to figure this out.

Re: iterating through collection in view

I'm back, and I found some unexpected behavior.

The snippet in questions was:

<%= safe_verification.safe_section.each { |a| a.total unless a.nil?} %>

The intent was to iterate over the children of safe_verification, and trigger a function for each in turn for output in the view.

After playing around with the console, I found that I could execute total on each and the results were sent to the console output.

I don't understand why, but the following does what I was intending:

<% safe_verification.safe_section.each { |a| concat "#{a.total.to_s} ", binding unless a.nil?} %>

So the output of the object reference was due to <%=.  In order for me to output the result of a function I have to use the concat function.

I think the basic problem was I was trying to get the output from a function to the view, and while this works, it seems really ugly to me.  I want the output of the function should be usable as a value by other functions for calculations and also for display in the view.

Am I going about this the wrong way?

Re: iterating through collection in view

OK, after some more experimentation, looks at the online ruby book, and a bunch more coffee:

I moved the code into a helper to cut down on clutter,

Here's the code that doesn't work

inside the helper file:
module SafeVerifyHelper
    def total_sections(sections)
        sections.each { |a| a.total } unless sections[0].nil?
    end
end

Here's code that does work:
module SafeVerifyHelper
    def total_sections(sections)
        size = sections.count
        sum = 0.00
        size.times { |i| sum = sum + sections[i].total }
        sum
    end
end

I put this out here so that if someone see a more idiomatic way of doing things in Ruby, or if this is appropriate, other newbies can see it.

Ed

Re: iterating through collection in view

Placing this in a helper is a good idea. However, you may want to move this into the SafeVerification model instead:

class SafeVerification < ActiveRecord::Base
    def total_sections
        size = sections.count
        sum = 0.00
        size.times { |i| sum = sum + sections[i].total }
        sum
    end
end

Then you can call it lke this:

<%= safe_verification.total_sections %>

As for cleaning up that method itself. Did you try my "inject" suggestion above? This might even work and it's much shorter:

def total_sections
  sections.collect(&:total).sum
end

Untested.

Railscasts - Free Ruby on Rails Screencasts

Re: iterating through collection in view

I did try the inject, but I got an error similar to the one for .each {}:

undefined method `+' for #<SafeSection:0xb719b8f8>

I don't understand exactly why this isn't working.  The collection is accessible as an array.  And I would think that calling .each would return a reference to the SafeSection object, which it seems to.  But the reference isn't directly usable.  It only works if we access it via [] and not the returned reference from each.


However, collect method that you just showed me is more concise and better looking.  Awesome! smile

Re: iterating through collection in view

If you want to use inject use this:

<%= safe_verification.safe_sections.inject(0) { |sum, a| sum + a.total } %>

This is the same as ryanb's code with 0 passed to the inject method. If inject is used without a parameter, sum will get the first element in safe_sections. And since a safe_section doesn't have a + method, you got the error undefined method `+' for #<SafeSection:0xb719b8f8>