RoR based MVC Web Frameworks – Friend or Foe?

Ruby on Rails has inspired a whole generation of MVC Web Frameworks. The RoR inspired frameworks follow the same basic design; Controllers whose public methods are web endpoints. So…Do RoR based MVC Web Frameworks encourage good design?

MonoRail and MS MVC are a part of the RoR generation.  

They must. Right? I show, in the code below, that RoR inspired frameworks can actually encourage bad design. I show, in the end, what I think is a better way.

The code presented here is written for MonoRail, but the same problems exist in MS MVC and RoR.  

Let’s start with a simple ProductsController. The index method of this controller is the implementation of http://whocares.com/products/<code&gt;. The code on the end of the URL is the code for a specific product (i.e. nj1287).

public class ProductsController : SmartDispatcherController{   public void Index(string code)   {      Product product = Product.FindByCode(code);      if(product == null)      {         PropertyBag["UnknownSearchTerm"] = code;         RenderView("common/unknown_quick_search");      }      PropertyBag["Product"] = product;      RenderView("display_product");   }}

CategoriesController is our next controller. The index method of this controller is the implementation of http://whocares.com/categories/<name&gt;. The name on the end of the URL is the name for a specific category (i.e. montiors).

public class CategoriesController : SmartDispatcherController{   public void Index(string name)   {      Category cat = Category.FindByName(name);      if(cat == null)      {         PropertyBag["UnknownSearchTerm"] = name;         RenderView("common/unknown_quick_search");      }      PropertyBag["Category"] = cat;      RenderView("display_category");   }}

The index method in both classes are basically the same. We can remove the duplication. Let’s extract an abstract class.

public abstract class SearchableController : SmartDispatcherController{   public void Index(string name)   {      ISearchable item = FindByName(name);      if(item == null)      {         PropertyBag["UnknownSearchTerm"] = name;         RenderView("common/unknown_quick_search");      }      PropertyBag["Item"] = item;      RenderView(IndexView);   }   protected abstract ISearchable FindByName(string name);   protected abstract string IndexView { get; }}public class ProductsController : SearchableController{   protected ISearchable FindByName(string name)   {      return Product.FindByCode(name);   }   protected string IndexView { get { return "display_product"; } }}public class CategoriesController : SearchableController{   protected ISearchable FindByName(string name)   {      return Category.FindByName(name);   }   protected string IndexView { get { return "display_category"; } }}

What do you think about our new DRY code?I don’t like it. Why?

  1. ProductsController and CategoriesController no longer “speak” to me.
  2. Inheritance. I really don’t like the template method pattern because of #1.

Can we fix this?We can use a DynamicAction in MonoRail. Let’s see that code.

Does RoR or MS MVC have a concept of a “DynamicAction”?  

public class IndexAction : IDynamicAction{   private ISearchableRepository repos;   private string indexView;   public IndexAction(      ISearchableRepository repos,      string indexView)   {      this.repos = repos;      this.indexView = indexView;   }   public void Execute(Controller controller)   {      string name = controller.Params["name"];      ISearchable item = repos.FindByName(name);      if(item == null)      {         controller.PropertyBag["UnknownSearchTerm"] = name;         controller.RenderView("common/unknown_quick_search");      }      controller.PropertyBag["Item"] = item;      controller.RenderView(indexView);   }}public class ProductsController : SmartDispatcherController{   public ProductsController()   {      DynamicActions["Index"] = new IndexAction(             new ProductsRepository(), "display_product");   }} public class CategoriesController : SmartDispatcherController{   public CategoriesController()   {      DynamicActions["Index"] = new IndexAction(             new CategoryRepository(), "display_category");   }}

I like this code better. We are no longer using inheritance to reuse code and the code “speaks” better to me. This code has two things that bug me.

  1. The controllers are really not doing much.
  2. DynamicActions are second class citizens in MonoRail.

How do we fix these issues? I don’t know how in the RoR inspired frameworks. I think we need another abstraction. Chris Ortman and I discussed the below code at HDC 07 (so he deserves at least part of the credit). I am not sure he agreed, but maybe this post will change his mind.

The code below is not apart of any framework. It is only an idea.  

public class IndexAction : SmartDispatcherAction{   private ISearchableRepository repos;   private string indexView;   public IndexAction(      ISearchableRepository repos,      string indexView)   {      this.repos = repos;      this.indexView = indexView;   }   public void Execute(string name)   {      ISearchable item = repos.FindByName(name);      if(item == null)      {         PropertyBag["UnknownSearchTerm"] = name;         RenderView("common/unknown_quick_search");      }      PropertyBag["Item"] = item;      RenderView(indexView);   }}

IndexAction is now a first class citizen of the framework. Actions have all the benefits that controllers have in MonoRail (parameter binding, …).How do we hook this action to a URL?

new Route("/products/<name>",    new IndexAction(new ProductRepository(), "display_product"));new Route("/categories/<name>",    new IndexAction(new CategoryRepository(), "display_category"));

Routing engines allow us to remove the “controller” completely. We don’t need a “controller” (at least not in the RoR framework form). What were our controllers doing? They were really just organizing our actions. Routing engines allow us to organize our actions without the “controller”.What do you think? I would really like some feedback on this topic. Does anyone else feel this pain with these frameworks?

Advertisements