Pragmatic Rails: Thoughts on Views, Inheritance, View Inheritance and Rails 3.0.4

{{{
While hacking on “Cells”:http://cells.rubyforge.org/ for Rails 3 with Yehuda earlier this year we were discussing if Cells’ view inheritance will be superseded by Rails.Yehuda patiently *postponed any work on it* with the words _”we will do that for you”_ 🙂 Apparently, he didn’t lie.

It seems that “view inheritance will be available directly in Rails 3.0.4”:https://rails.lighthouseapp.com/projects/8994/tickets/948-template-lookup-should-search-controller-inheritance-chain, which is a fantastic improvement for Rails.

In this post I’d like to discuss
* *What _is_ view inheritance* and how does it help?
* *What’s the problem* when view inheritance is tightly bound to controllers?
* *How shared components* with inheritance can help

h3. What is View Inheritance?

Say we have a @PostsController@ in a blog app. *It shows a list of posts.* Wow. We also have a derived @PrivatePostsController@, here’s the inheritance chain.

PrivatePostsController < PostsController < ...

Both controllers have an @#index@ method.

class PostsController < ApplicationController
  def index
    @posts = Posts.public_posts_for_all_of_yer
  end

class PrivatePostsController < PostsController
  def index
    @posts = Posts.private_posts
  end

While the @PostsController@ *_does_ have* an @index@ view, its child @PrivatePostsController@ *doesn’t*.

|-- posts
|   |-- index.html.haml
|-- private_posts
|-- backend

Now, when the private controller wants to render its @index@ view, it first looks in its own views directory. *If it can’t find a suitable view, it just travels up the inheritance chain.* It finds the parent’s @index@ view, and uses this.

*Cool, this is view inheritance.*

h3. Inheritance and partials?

The newly introduced mechanism *also applies to partials*, which is pretty awesome.

If the @index@ view would use a partial to show a list of navigation links, *the @PrivatePostController@ could “overwrite” just this piece of the page* by dropping a separate partial in its view directory.

|-- posts
|   |-- index.html.haml
|   |-- _navigation.html.haml
|-- private_posts
|   |-- _navigation.html.haml

*Overriding view pieces done simple.* I’m happy this finally found its way into Rails! Good job, artemave.

h3. Use case: Sidebar widgets

Usually we need *shared partials whenever we want a reusable view component.*

A sidebar box showing links to unread posts.
}}}
{{{
What about a status box in our app exposing helpful links to the reader? If an *admin user* is logged in, he (or her) will *get links to _edit_ new, unread posts* and his own drafts.

An *editor* might see *links to _read_ new posts* assigned to him. His job is just proof-reading.

This is a typical setup in almost every Rails app, *small boxes display different informations according to the authenticated user*.

Usually *this ends up in a helper that delegates to different partials*, where lots of code is put into that partials.

def render_status_box
  if current_user.admin?
    render :partial => "shared/admin_box"
  elsif current_user.editor?
    render :partial => "shared/editor_box"
# and so on...

The problem here is, *you simply cannot use inheritance anywhere.* Neither is it possible to derive helpers (they are modules), nor could view inheritance be applied here.

h3. You can’t inherit shared partials

Why that? Well, *the shared partial and the rendering controller are not related in any way.* Nevertheless, the _controller’s_ ancestor chain is inspected when trying to find an “inheritable” shared partial, *which is simply the wrong place*.

In other words, the partial lookup will climb up the inheritance chain of the controller _using_ the partial, and that’s *definitly the wrong place to search*.

The bottom line: *When it comes to reuseable view components, Rails’ view inheritance is still insufficient*.

h3. Reuseability with inheritance

So, what do to now? The answer, as usual: *Divide and conquer.* Split your view into object-oriented components and get back the power of inheritance.

You’re still with me? *Cool.*

First, I create *a cell that provides the basic functionalities* – generating a user greeting and compiling a list of links for the user.

class UserCell  :box
  end
  
  def user_greeting
    "#{current_user.name} (#{current_user.role})"
  end
  
  def links
    current_user.unread_posts.collect do |p|
      [p.title, post_path(p)]
    end
  end
end

If you’re new to cells, you might read “this introducing post”:http://nicksda.apotomo.de/2010/11/lets-write-a-reusable-sidebar-component-in-rails-3/.

h3. How to get that in my app?

Now I embedd the cell in the application layout.

  #sidebar
    = render_cell :user, :box

This basically calls the @#box@ _state_ of our @UserCell@, which

* *compiles the greeting message* in @#user_greeting@. Note how this *removes the need for pushing complex helpers* into the view.
* *prepares a list of post links* in @#links@. While we could do the setup in the view, too, I prefer *placing that kind of code to the controller*, since its job is aggregating data. It also *improves unit-testability* and makes it easy to inherit and override!

The @render@ statement finally parses the cell’s view.

|-- app
|   |-- cells
|   |   |-- user
|   |   |   |-- box.html.haml

The box view could look like this.

  Hey, #{@user_greeting}, how are you?
    
  Check that:
  
  %ul
    - @post_links.each do |p|
      = %li #{link_to *p}

A sidebar box showing links to unread posts.
And we get a *list of links pointing to posts* which the logged in user hasn’t read, yet. Yeah.

h3. Override a helper, inherit a view!

The admin box shows links for editing posts.

When an admin user is logged in, we want a slightly different list, *showing links to _edit_ posts*.

Let’s use inheritance to do just that!

class AdminCell < UserCell
  def links
    current_user.pending_posts.collect do |p|
      [p.title, edit_post_path(p)]
    end
  end
end

This is all, we just overwrite the helper. *Everything else is inherited from the @UserCell@.* The methods, the states, the views.

h3. No deciders in your views!

Now, *your application layout shouldn’t end up with deciders* figuring out which cell to render.

#sidebar
  - if current_user.admin?
    = render_cell :admin, :box
  - elsif current_user.editor?
    = render_cell :user, :box

Cells provides you with *a builder to prevent your view from being cluttered*.

class UserCell < Cell::Rails
  build do |opts|
    AdminCell if current_user.admin?
  end

That’s all! Now, calls to @#render_cell(:user, …)@ will *query the builder and internally create the correct cell* for you.

h3. It’s more than a feature

I hope the examples showed how view inheritance can help *cleaning up your code* while providing an object-oriented *view layer with real inheritance*. This is nothing exotic – many “cells”:https://github.com/apotonick/cells users have reported to use the techniques discussed in this post successfully.

And now, lemme quote my friend Kevin from Austin: _Merry Christmas and all that stuff!_
}}}

Advertisements

13 thoughts on “Pragmatic Rails: Thoughts on Views, Inheritance, View Inheritance and Rails 3.0.4

  1. @Rami: Your example still suffers from a DoubleRenderError which is why Cells were invented 4 years ago.

    So, Cells are derived AbstractControllers as well and use “native” rails methods as described in the blog post. However, Cells streamline the process of having reuseable controllers throughout your app (as it should be in a real MVC framework) and provides additional features like clean caching and using #params in your cell, which is not possible in your example.

    Don’t get me wrong – Cells is nothing special and I really hope that cells becomes superseded by a clean and refactored rendering API in Rails in the near future!

    Like

  2. Hi Fernando,

    You are right about being a godd thing trying to minimize coupling between parent and children types. So let’s try to refactor out in a way that the parent class does not hold a reference to its children?

    class GenericCell < Cell::Rails
      build do |opts|
          return AdminCell if current_user.admin?
          UserCell
      end
    end
    
    class AdminCell < UserCell
     
      def links
        current_user.unread_posts.collect do |p|
          [p.title, post_path(p)]
        end
      end
    end
    
    class UserCell  :box
      end
     
      def user_greeting
        "#{current_user.name} (#{current_user.role})"
      end
     
      def links
        current_user.unread_posts.collect do |p|
          [p.title, post_path(p)]
        end
      end
    end
    

    EDIT (nick): fixed that 😉

    Sorry if there is no formatting in the code.

    Cheers

    Like

  3. @Fernando: I absolutely agree. So Guilherme’s approach is a dedicated configuration cell, which is GenericCell here.

    Another trick would be putting the builder code in the actual sub-cell:

    class AdminCell < UserCell
      supercell.build do
        # set things up here
      end
    

    This would work if the derived cell is required explicitly, otherwise the builder isn’t executed until the sub-cell is loaded.

    You could also put the builder code in a separate configuration module and require that in an initializer.

    As you might see, there are plenty of ways to prevent “OOP mess” 😉

    Like

  4. Great post, now I understand view inheritance (wasn’t clear to me at first). And hope you had that merry Christmas and all the other stuff!

    @Rami: one thing Nick left out was the testability of Cells versus partials. Cells can be tested in isolation whereas it’s impractical to test partials.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s