3

I am new to Ruby and Rails (switched from Python and Python frameworks). I'm writing a simple dashboard website which displays information about the S.M.A.R.T. state of hard disks. Here I wrote a helper to display a badge in a table cell near the relevant S.M.A.R.T attribute if it's value meets a condition. At first, the helper code was as simple as in Listing 1, but then I decided to draw a summary of all badges for the specific drive, in addition to the badges near individual S.M.A.R.T. attributes in the table. So at first I added a simple method like:

def smart_chk_device(attrs)
    attrs.each { |item| smart_chk_attr(item) }
end

But this approach didn't work and it caused the entire array of attributes to be output to the resulting page. It only started to work when I made it as in Listing 2, but I believe there's something wrong there, and the same thing can be done in a more simple way. Please show me the right, Ruby-way of doing it.

Listing 1:

module HomeHelper
    def smart_chk_attr(attr)
        case attr[:id].to_i
        when 1,197
            content_tag(:span, "Warning", :class => "label label-warning") if attr[:raw].to_i > 0
        when 5,7,10,196,198
            content_tag(:span, "Critical", :class => "label label-important") if attr[:raw].to_i > 0
        end
    end
end

Listing 2 (works, but I don't like it):

module HomeHelper
    def smart_chk_attr(attr)
        case attr[:id].to_i
        when 1,197
            return content_tag(:span, "Warning", :class => "label label-warning") if attr[:raw].to_i > 0
        when 5,7,10,196,198
            return content_tag(:span, "Critical", :class => "label label-important") if attr[:raw].to_i > 0
        else
            return String.new
        end
        return String.new
    end

    def smart_chk_device(attrs)
        output = ""
        attrs.each { |item| output << smart_chk_attr(item) }
        return output.html_safe
    end
end

attrs is an Array of Hashes, where each Hash contains keys :id and :raw with the numeric code of a S.M.A.R.T attribute and its RAW value, both in Strings.

Also, RoR complaints if to remove the last "return String.new" in Listing 2. Why is it so? Doesn't the "case" block all the possible cases, so that control should never reach the end of the function?

1
  • its more ruby-like to use real words instead of 'chk' for starters. And String.new? Do you just mean ''? Commented Aug 16, 2012 at 20:59

2 Answers 2

3

I believe this would behave in the same way, and is much shorter:

module HomeHelper

  def smart_chk_attr(attr)
    return '' unless attr[:raw].to_i > 0
    case attr[:id].to_i
      when 1,197
        content_tag(:span, "Warning", :class => "label label-warning")
      when 5,7,10,196,198
        content_tag(:span, "Critical", :class => "label label-important")
      else ''
    end
  end

  def smart_chk_device(attrs)
    attrs.map { |item| smart_chk_attr(item) }.join.html_safe
  end

end

Ruby methods return the value of the last expression, so get rid of the explicit returns all over that method. Also, DRY: Don't Repeat Yourself (the attr[:raw] check). In this case, I replaced those with a guard clause at the start of the method. Short-circuiting guard clauses are a matter of taste, but I like them and you'll see them in a lot of Ruby code.

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

3 Comments

As a note, using String.new is highly irregular and is best done with '' instead, just as Array.new is [ ] and Hash.new is { } much like JavaScript.
Generally the when clauses are considered to be the same level as the case so that the optional else condition can be at the same level as in if...end. Putting the return of the else on the same line is misleading as it sort of looks like a case. It might be possible to use nil instead of empty string, reducing this even further.
Ruby code style tends stick to certain conventions more closely than other languages, but it's still mostly a matter of personal opinion. I prefer indented cases, for example. When contributing to someone else's project, follow their coding style. Otherwise, stick to what is most readable to you --- after all, trivialities like tabs vs spaces, brackets vs do/end, and (un)parenthesized method calls are secondary to having functional code.
0

Your method smart_chk_attr(attr) has an extra return at the end, it will never get run.

The each is an enumerator, when it finishes going through each item that you supplied it returns the the original object passed into it, not the modified stuff inside.

If you use collect you will get an array with your modified objects. If you are wanting a string output you can use join to put them into a string. Join will also take an option for how to join your items.

def smart_chk_device(attrs)
  attrs.collect{ |item| smart_chk_attr(item) }.join.html_safe
end

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.