Dirty Views? Clean them up!

Posted on March 27, 2007

I feel the area where I need to improve most is in cleaning up my views. I feel like I’ve done a good job cleaning up my controllers and models, thanks to posts like Skinny Controller, Fat Model.

I have known about the content_for in rails views. Which basically allows you to have a layout like so:

<html>
    <head>
    </head>

    <body>
        <div id="wrapper">
            <div id="header">
                <% yield :header %>
            </div>

            <div id="content">
                <%= yield %>
            </div>

            <div id="sidebar">
                <% yield :sidebar %>
            </div>

            <div id="footer">
                <% yield :footer %>
            </div>
        </div>
    </body>
</html>

And in your view the following:


  <% content_for :header do %>
       This would appear for the yield :header call
  <% end %>

  <% content_for :sidebar do %>
      This is the sidebar content
   <% end %>

  <% content_for :footer do %>
     This is the footer content
  <% end %>

   <%# the rest would just be the content for the plain yield call %>
    This is would be my main content

This is nice, but I wanted to find a way to supply default content and this is what I came up with.

  def yield_with_default(yield_on, content=nil, &block)
    yield_content = eval "@content_for_#{yield_on} || ''" 
    if block_given?(&block)
      concat(yield_content.empty? ? capture(&block) : yield_content, block.binding)
    else
      Binding.of_caller do |binding|
        concat(yield_content.empty? ? content : yield_content, binding)
      end
    end
  end

I placed this in my application_helper.rb file. And now I can add something like this to my view.


    # with a block
    <% yield_with_default :header do %>
        This is my DEFAULT header content
    <% end %>

   # without a block
   <% yield_with_default :header, "This would be my default content" %>

This feels pretty clean to me. But if anybody has any other suggestions I would love to hear them.

I am also looking for more information on best practices with views in Rails. There doesn’t seem to be much information on the subject.

UPDATED: 04/01/2007 – forgot the do in the content_for statements

Comments
  1. DougMarch 28, 2007 @ 12:59 AM

    Another good post, I like the yield_with_default. As far as more best practice posts about views the only other one I know of off hand is from Err – you’ve probably seen it already, but just in case: http://errtheblog.com/post/28

  2. zerohaloMarch 28, 2007 @ 04:44 AM

    I think I may be having a brain cloud right now, but I don’t see how this would benefit you if you just add yield_with_default to a specific view. Wouldn’t you need to add it to your application.rhtml in order for the header to be used in any view that calls the layout with the yield :header call? And in that case, why not make it a separate partial called by the layout? I’m probably missing the point altogether :-)

  3. Graeme NelsonMarch 28, 2007 @ 05:52 AM

    Doug – thanks for the link. I have looked at that post before but forgot about it.

    Zerohalo – The yield_with_default calls exist in the the main layout file, in this case the application.rhtml file. Then in my view, like /app/views/people/new I would call the following if I want to overwrite the default header behaviour in the application.rhtml file:

    &lt;% content_for :header do %&gt;
       some header content
    &lt;% end %&gt;
  4. Joe Van DykMarch 28, 2007 @ 06:39 AM

    Check out haml. http://haml.hamptoncatlin.com/

    Looks really sweet.

  5. Graeme NelsonMarch 28, 2007 @ 04:33 PM

    Joe – I have looked at haml and it looks promising. The issue with moving to haml is that our developers and designers would need to learn a new markup. I wanted to find a best practice with erb/html. I do plan on trying out haml on a project at home.

  6. Dan KubbMarch 29, 2007 @ 06:42 AM

    One thing I do that cleans up my views is I create 2 helper methods for each attribute I display on a page, one for showing it and one for editing it. I use a naming convention where I prefix either edit or show onto the name.

    So if I have a form that asks for someone’s name, email and country I’d have three helper methods edit_name, edit_email and edit_country.

    The edit_name and edit_email methods would return text boxes with the maxlength set to the same value as validates_length_of would check; or the database column length, whichever is less. I’d also make sure other attributes like title are set too.

    The edit_country method would return a drop-down box of all the countries along with the priority countries set as well.

    Sometimes I even take it a step further and create a simple custom form builder so I can use form_for with the helpers.

    Form views can tend to get messy, but I’ve found this cleans things up and results in more consistent forms across an app.

  7. MarkMarch 29, 2007 @ 07:14 AM

    http://markaby.rubyforge.org/

  8. Ryan BatesMarch 29, 2007 @ 09:51 PM

    I prefer to put the default in a partial and render that if yield returned nil:

    yield(:side) || render(:partial => ‘default_side’)

  9. Graeme NelsonMarch 29, 2007 @ 11:34 PM

    Dan – I’ll have to admit that I haven’t used much helpers in the past. They were out sight out of mind for me, until recently. I am excited to use them more and come up with some good practices. I will take your suggestions to heart. I also use the Custom Form Builder, it really helps to clean up the views.

    Mark – thanks for the link. I will check it out.

    Ryan – The funny thing is that I tried that and it didn’t seem to work for me. I am sure it was a typo on my part. Or, maybe I just wanted a reason write my own Block Helper. I haven’t decided what version I like best, currently I like my version ~ maybe a little biased ~

  10. supaspoidaApril 24, 2007 @ 07:04 PM

    Great article! This is definitely the nicest way I have seen to deal with layouts and templates while still keeping things as DRY as possible.

    I did have one question though. Is there any way to get this to work when calling a parent view using render :template and then using the :action view to populate yields within the template? I have not been able to get this to work in my app. Any help would be much appreciated.

  11. Graeme NelsonApril 24, 2007 @ 07:30 PM

    @supaspoida – what are you trying to accomplish? do you have an example? btw, thanks for the comment.