Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the elegant solution for unrelated views in MVC web frameworks?

I've had a problem with the following issue in Rails and ASP.Net MVC. Often there are multiple widgets of functionality on a page, yet one controller action is supposed to render the page. Let me illustrate:

Let's say I have a normal e-commerce site, and the menu is made of categories, while the page is to display an group of products.

For the products, let's say I have an action on a controller that looks something like:

def product_list
     @products = Products.find_by_category(:name => 'lawnmowers')
end

And I have a layout with something like

<div id="menu"><%= render :partial => 'menu' %></div>
<div id="content"><%= yield %></div>

The products have a view...

<%= render :partial => 'product', :collection => @products %>

(note I've ommited the product view as irrelevant)

And the menu has a partial...

<% Category.each {|c| %>
   <%= render :partial => 'menu_node', :locals => { :category => c } %>
<% } %>

The line I have a problem with is the "Category.each.do" in the view. I'm fetching data in the view, as opposed to using variables that were set and bound in the controller. And it could easily be a more complex method call that produces the menu.

The solutions I've considered are:

-A view model base class that knows how to get various pieces of data. But you could end up with one of these for each conceptual "section" of the site.
-a local variable that populates at the top of each method (violates DRY) -the same thing, but in a before_filter call

None of these seem very elegant to me. I can't help but look at this problem and think that a MVP presenter per view (not screen) is a more elegant solution.

ASP.Net MVC has render action (different from rails render :action), which does address this, but I'm not sure what I think of that solution.

Thoughts? Solution suggestions?

Added Note: The answers provided so far are good suggestions. And they apply to the example I gave, where a menu is likely present in every layout, and is clearly secondary to the product data.

However, what if there is clearly no second class citizen? Portal type sites commonly have multiple unrelated widgets, in which each is important.

For example, What if this page was displaying weather trends, with widgets for temperature, humidity, and precipitation (and each is a different model and view type).

like image 440
Tim Hoolihan Avatar asked Aug 02 '10 19:08

Tim Hoolihan


2 Answers

In rails we like to have a concept of thin-controllers, thick-models. So I think you're right to not want to have variables set in the controller.

Also, in order to enable a more-complex method later on, I recommend doing something like:

/app/controllers/application_controller.rb

before_filter :add_menu_nodes

def add_menu_nodes
  @menu_nodes = Category.menu_nodes(current_user)
end

/app/views/layouts/application.html.erb

<%= render :partial=>:menu, :locals=>{:categories=>@menu_nodes} %>

/app/models/category.rb

def self.menu_nodes(current_user)
  Category.all.order(:name)
end

That way in the future you could update Category.menu_nodes with a more complicated solution, based on the current user, if you need.

like image 76
Jesse Wolgamott Avatar answered Oct 12 '22 09:10

Jesse Wolgamott


Forgive me if I butcher the Ruby (or misunderstand your question), but what's wrong with

class section_helper
    def menu( section )
        // ...
        menuBuiltAbove
    end
end

in the view

<%= section_helper.menu( 'section' ) %>

?

like image 29
µBio Avatar answered Oct 12 '22 10:10

µBio