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

Learn to love your models

Posted on March 06, 2007

When I firsted started writing Rails Apps, coming from the Java world, I forgot all my good OOP training. Within Rails, I viewed the model as this magically class that gave me all this power and I was afraid to add any of my own behaviour to the model. Which went against my previous way of doing things, which was to place as much of the business functionality within the model.

Well, I am glad Jamis came along and wrote this post.

I am back on track. I don’t know how many times I wrote something like this in a controller:

   def update
       @user = User.find(params[:id])
       @subscription_plan = SubscriptionPlan.find(params[:plan_id])
       @subscription_plan.update_attribute(:status, 'active')

       # deactivate previous plans
       @user.subscription_plans.each {|plan| plan.update_attribute(:status, 'disabled') if plan.status == 'active'}
       @user.subscription_plans << @subscription_plan
   end

This is just ugly. And goes against a lot of OOP best practices. The controller knows too much about the internals of the User and Subscription Plan models. Also, if I wanted to test this controller action using mocks and stubs, I would have to write a bunch of code. What if I fixed the controller code to be like so:

   def update
     @user = User.find(params[:id])
     @subscription_plan = SubscriptionPlan.find(params[:plan_id])
     @user.upgrade_to_subscription_plan(@subscription_plan)
   end

This is much cleaner and easier to mock/stub. Also, if I decided to change the behavior of how a user upgrades their plan, the upgrade_to_subscription_plan method isolates any changes from the controller action – as long as the method signature doesn’t change.

So, go ahead a love your models.