Topic: Make this view code shorter

DISCLAIMER:  I've only been working in ruby and rails for a few days.  I know almost nothing.

At any rate, just noticed this pattern in the view code for a particular model (show):

<p>
  <b>Size:</b>
  <%= @order.size %>
</p>

<p>
  <b>Price:</b>
  <%= @order.price %>
</p>

etc...

It seems to me that the pattern is thus:

<p>
  <b>NAME_OF_INSTANCE_VAR:</b>
  <%= @order.INSTANCE_VAR %>
</p>

What I'd like to do is have some generic function, method, helper, whatever, which would cycle through the instance vars of the object and output the above code snippet.  Something like this (pseudocode):

<%= foreach INSTANCE_VAR order do: %>
<p>
  <b>name(INSTANCE_VAR):</b>
  <%= @order.INSTANCE_VAR %>
</p><%= end %>

I could not only make this one particular view a lot shorter, but I could use the same method, function, whatever, in other views too.  Thing is, not only do I not know how to get this list of instance vars, I also don't know the way to metaprogram this in Ruby.  I could easily do it in TCL right now without even thinking.  I could easily do it in a few other languages.  But I don't know how to do it in Ruby.

I guess the first question is, is there already some Rails function that will already do this for me?  I'd be surprised if there wasn't, but then again it doesn't use said theorized function in the views it auto-generates, so I guess the answer is no.

Second question would then be, how does the rails community tend to handle this?

Thanks.

EDIT:

I guess something like this?

<% @order.attributes.each do |key, value| %>
    <%= content_tag :b, key+":" %>
    <%= value %>
<% end %>

Last edited by rex.goxman (2012-05-28 16:07:37)

Re: Make this view code shorter

There are two ways to go,  using  a view helper,  or creating a custom builder.

I always use the Builder::XmlMarkup when I create HTML in a helper or controller or in a custom builder.

app/helpers/application_helper.rb:

require 'active_support/builder' unless defined?(Builder)  
def showAttrs(inst)
   xm = Builder::XmlMarkup.new
   klass = inst.class
   klass.column_names.each do |n|
     xm.p { 
       xm.b { xm << klass.human_attribute_name(n) }
       xm << klass.send(n)
     }
   end
   xm
end

Then in any view you could do:

<%= showAttrs(@order) %>

Or the custom builder approach

require 'active_support/builder' unless defined?(Builder)  
class MyBuilder < ActionView::Helpers::FormBuilder
   def  showAttrs
     xm = Builder::XmlMarkup.new
     @object_name.column_names.each do |n|
       xm.p { 
         xm.b { xm << klass.human_attribute_name(n) }
         xm << @object_name.send(n)
       }
     end
     xm
  end
end     

Then you could do

<%= form_for @order , :builder=>MyBuilder %>
  <%= raw f.showAttrs %>
<% end %

Last edited by BradHodges (2012-05-28 19:06:14)

Joe got a job, on the day shift, at the Utility Muffin Research Kitchen, arrogantly twisting the sterile canvas snout of a fully charged icing anointment utensil.

Re: Make this view code shorter

I think using of all attributes or column_names is wrong approach because you will have some only internal information like id, created_at, product_id and so on.
Also the problem is in not matching field names and headers that you should show in future.
I propose to create array of hashes for each model for now, in future customization it could be moved to helper.
Something like:

class Order < ActiveRecord::Base
  DISPLAYED_FIELDS = [{:price => 'Price'}, {:created_at => 'Creation Date'}, {:quantity => 'Ordered Quantity'}]
.....
end

After that you can create template:
app/views/orders/_display_item.html.erb

<p>
  <b><%= "#{item.value}:" %></b>
  <%= @order.send(item.key) %>
</p>

And use it directly in view or in helper:

<% Order::DISPLAYED_FIELDS.each do |item| %>
 <%= render 'display_item', :item => item %>
<% end %>

Last edited by KindBug (2012-05-29 04:27:47)

Re: Make this view code shorter

Also you can add rendering of whole object info in one partial:
app/views/orders/_object_info.html.erb

<% object.displayed_fields.each do |item| %>
 <%= render 'display_item', :item => item %>
<% end %>

In that case you should add method that return constant to model:

class Order < ActiveRecord::Base
  def displayed_fields 
    DISPLAYED_FIELDS
  end
end

But I think it is better to set it as class method in that case:

<% object.class.displayed_fields.each do |item| %>
 <%= render 'display_item', :item => item %>
<% end %>
class Order < ActiveRecord::Base
  def self.displayed_fields 
    DISPLAYED_FIELDS
  end
end

Last edited by KindBug (2012-05-29 04:36:27)

Re: Make this view code shorter

Also I think:

  <p>
    <b>Price:</b>
    <%= @order.price %>
  </p>

Is not best markup decision. In that case I think more proper is definition list:

<dl>
  <dt>Price:</dt>
  <dd><%= @order.price %></dd>

  <dt>Quantity:</dt>
  <dd><%= @order.quantity %></dd>
   .....
</dl>

Re: Make this view code shorter

Thanks for the responses - I will review them.

Also, it appears that the edited solution I threw up there before any responses works.  Presuming it was factored out to make it "generic," what are thoughts on this solution?

Again, thanks.

Re: Make this view code shorter

I think using of all attributes or column_names is wrong approach because you will have some only internal information like id, created_at, product_id and so on steel board fence Alberta.