Listing Attributes

This past week I was working on a Ruby on Rails project where I needed to list out a lot of attributes of an object. The difficult part came in the fact that I didn't want to display the attribute if it wasn't set or was 0.

Originally I had:

<% if @object.my_attribute && !@object.my_attribute.blank? %>  
  <li>  
    <strong>Attribute Label</strong>  
    <span><%= @object.my_attribute %></span>
  </li>  
<% end %>

When you start talking about 25 or 30 attributes you are printing out, some of which are nested, this gets to be a really ugly view.

The next step was to move this to a helper method.

<%# in the view %>
<%= print_attribute("Attribute Label",@object.my_attribute) %>
# view helper
def print_attribute(text,attribute)
  name = content_tag :strong, text
  prop = content_tag :span, attribute
  content_tag :li, name + prop
end

This is better, but gets messy when I needed to change the formatting on some attributes and other attributes needed to be printed in a different format (percentages, dollar values, etc).

I wanted the solution to be as easy as defining hash of attributes and titles. That way I could just grab my attributes:

# view helper
def all_attributes(object)
  {
    'my_attribute' => 'Attribute Label',
    'nested_object.another_attribute_string' => 'Another Attribute'
  }
end

In order to define the additional information I created some *_string methods which would return the right format or nil (which would get ignored later). For example:

def another_attribute_string
  return nil if another_attribute.blank?
  "#{another_attribute} ft."
end

I wanted to be able to control the way they got printed in the view since it changed depending on which view I was in. My goal was to be able to write the following.

<% a = all_attributes_for(@listing) %>
<% attributes_for_view(@listing,a).each do |key,value| %>
  <li>
    <%= content_tag :span, key %>
    <%= content_tag :strong, value %>
  </li>
<% end %>

Thus the method needed to return a hash which had the correct label (key) and value (value).

def attributes_for_view(obj,attributes)
  # This first inject will return the hash we want, with the value
  # of the label as the key and the attribute as the value
  attributes.to_a.inject({}) do |r,attribute_pair|
    # In order to do this, we first need to get the attribute.
    # We do the split in order to get nested objects. But we are
    # always starting with our root object, `obj`.
    value = attribute_pair[0].split(".").inject(obj) do |obj, attribute|
      obj.send(attribute)
    end

    # Skip this value if it is nil
    unless value.nil? 
      # Strings we want to keep as is
      if value.is_a?(String) && !value.blank?
        r[attribute_pair[1]] = value
  
      # For us, most of our numbers where dollar values. 
      # So rather than creating a custom `_string` method for each, 
      # it was easier to handle the case where the number 
      # shouldnt' be a dollar value.
      elsif value.is_a?(Integer) && value > 0
        r[attribute_pair[1]] = number_to_currency(value)
      end
    end

    # return the hash to continue building
    r
  end
end

A couple injects later I have a clean function that does exactly what I wanted.

Ryan Mathews

Ryan Mathews

San Francisco, CA