Archive for the ‘Cells’ Category

Using Cells View Inheritance to clean up your views

Monday, April 12th, 2010

To the right!

In every Rails app is some reoccurring element.

In my blog example, this is a small teaser box (to the right!) showing a snippet from the newest post, and a footer section that provides a link to the article.

The teaser on my RubyFlow portal page.

However. My imaginary blog also has a page about RubyFlow where I’d list the links I posted there, just to proof how cool I am.

On the RubyFlow page, I don’t want the teaser to show a link to the article, but a red button to RubyFlow. Of course, this doesn’t make sense at all.

My god, I hate that red in combination with the pink on this site.

The medieval if-cascade

Things like that are usually implemented in the partial with some if/else part.

if @current_page == 'rubyflow'
  Don't read it, better go to rubyflow
else
  = link_to "Read it!", "/blog/read/#{@post.id}"

Pretty ugly.

Things start getting really ugly when there are more conditions to respect and end up in a medieval if-cascade. It’s geting even worse when those deciders are copied and spread across multiple files.

How we do it

Cells brings back the power of OOP to your views. It offers a feature called View Inheritance that saves us here. I will briefly discuss that while showing you how I implemented the different teasers.

$ script/generate cell teaser box body footer --haml
      create  app/cells/teaser_cell.rb
      create  app/cells/teaser/box.html.haml   
      create  app/cells/teaser/body.html.haml
      create  app/cells/teaser/footer.html.haml

The TeaserCell has three states that don’t do much right now.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TeaserCell < ::Cell::Base
  def box
    @post = Post.find :first
    render
  end
 
  def body
    render
  end
 
  def footer
    render
  end
end

Just grabbing the newest post in line 3.

Nesting states

I use the box state as a container. It renders the body and footer in its view app/cells/teaser/box.html.haml.

#teaser
  %h3
    Nick says
 
  .body
    = render :state => :body
  .footer
    = render :state => :footer

That’s right, I use render :state in a cell view. That’s somehow comparable to rendering a partial, although it first executes the #body method, which in turn renders its view.

teaser-with-generic-body-and-footer

That’s how the teaser currently looks like as we didn’t customize the body and footer view, yet.

It is pretty obvious that our body view should print out the teaser text, whereas the footer displays the link.

app/cells/teaser/body.html.haml

%h2 #{@post.title}
%p  #{@post.body}

And the footer in app/cells/teaser/footer.html.haml.

= link_to "Read it!", "/blog/read/#{@post.id}"

Enriching the controller

In the blog view I then call #render_cell to integrate the teaser in my blog ERB page.

<div id="right_menu">
  <%= render_cell :teaser, :box %>
</div>

Ok, so now we got a beautiful teaser in the blog page. How do we get that teaser to show a red button on the RubyFlow page?

Inheritance strikes again!

The answer is - unsurprisingly: we derive a new RubyflowTeaserCell from the TeaserCell.

$ script/generate cell rubyflow_teaser footer --haml
      create  app/cells/rubyflow_teaser_cell.rb
      create  app/cells/rubyflow_teaser/footer.html.haml

The actual inheritance happens in the class.

class RubyflowTeaserCell < TeaserCell
end

Note that we don’t need any methods as they all come from TeaserCell. The same applies to the views, they’re inherited as well!

But wait, we planned on overwriting the footer so here’s how the app/cells/rubyflow_teaser/footer.html.haml looks like.

Don't read it, better go to
%span rubyflow

In the Rubyflow controller view I add the cells rendering.

<div id="right_menu">
  <%= render_cell :rubyflow_teaser, :box %>
</div>

That’s a call to RubyflowTeaserCell. Now, what happens here.

  • the derived RubyflowTeaserCell calls the #box method which is inherited
  • as the respective view is not found in app/cells/rubyflow_teaser it is looked up in the parent directory app/cells/teaser/ and found
  • the same happens with the :body state
  • when rendering the footer, the view is found in the cells own view directory and thus not inherited

Conclusion

View inheritance comes into play whenever complex deciders threaten the straightforwardness of your view code. When you encounter if-cascades with more than two ifs, think about using a cell with inheritance there.

Code?

The example code can be found at github.

Cells gemified!

Friday, February 5th, 2010

We finally managed to push Cells to gemcutter, the key repository for Ruby gems.

Installing Cells is now as simple as

gem install cells

To load the gem in your Rails application you might extend your configuration in config/environment.rb:

Rails::Initializer.run do |config|
  config.gem "cells"

That’s all! Isn’t that great?

What did we do?

The question here is: What did Jonas Grimfelt do?

That’s this swedish guy who suddenly appeared in the project and brutally accelerated the gemify process with his incredible knowledge about gems, Rails plugins and conventions therefore.

Well, it was mostly him who

  • refactored the plugin to be a grown-up standalone gem that doesn’t need a daddy rails project
  • moved and tuned numerous files so they can be found by the autoloading
  • continuously urged me to fix all the tests
  • continuously urged me to fix things

The result is an enterprise cells gem. And the best: It’s even working!

Magic sucks – the new #render is explicit!

Thursday, December 24th, 2009

Having discussed the pros and cons thoroughly we finally decided to have an explicit #render in Cells.

Explicit code is the opposite of implicit – or magic – code. Evangelists of magic code often emphasize that their approach saves people from writing too much code. In place of user code, the library magically guesses what the programmer wants and invokes methods for him.

In Cells a state method usually worked by executing the state logic and then returning nil or calling #render.

def my_state
  do_something
  nil # instructs the outer method to automatically render my_state.html.
end

However, this #render was just a fake assignment and did not return the rendered view, as the name would imply. It just instructed the outer method to render the view. This is a perfect example for implicit code. You know that from the ActionController’s render cycle.

The new #render

When turning the old render cycle into an explicit mechanism we could throw away code. I claim that throwing away library code is a good thing.

User code will usually keep working.

This will work, since we return the rendered view.

def my_state
  render
end

Remember, #render really returns the markup for the view, so you might modify it.

def my_state
  markup = render
  „<p>+ markup +</p>end

Rendering your state view three times in a row is not a problem at all. No DoubleRenderError involved!

def my_state
  render + render + render
end

Even cooler, you can also use #render in your cell view, again. This replaces the obsolete concept of partials. We don’t treat partials in a special way in Cells anymore.

<p>
  We got <%= @count %> new users today.
  <%= render :view => 'top_user', :locals => {:user => @top_user} %>

Note that render :view => ... will respect view inheritance!

Here’s a short overview of more new features:

Did you know you can use layouts in your cell state? This could make your cell even more DRY.

render :layout => 'collapsable_widget'

Here, collapsable_widget.html would usually reside in app/cells/layouts.

In the former API you would just return a string as view. We now have the less magical :text parameter.

render :text =><p>Awesome stuff!</p>

Of course, this will work as well and you can save some markup.

render :text => „Awesome stuff!“, :layout => 'p_block'

For those who want the full power of templates without having templates.

render :inline => "<h1>Hey <%= @name %>!</h1>", :layout => 'page'

Maybe you want to render a complete new state on the cell instance. This would execute the state method and then return the rendered view within the current state.

def form
  setup_form
  @text_input = render :state => :text_field
 
  render
end
 
def text_field

Sometimes it’s handy to have local variables in the view.

render :locals => {:scoped_user => user_for_cell}

You can tell render which type of template you want, e.g. when distinguishing between request types.

render :template_format => :js

And sometimes it’s nice to simply shut up.

render :nothing => true

Have great holidays!

Cells for Rails 2.3 is out!

Saturday, March 28th, 2009

Cells bring back components to Rails, as superfast mini-controllers plugged into views or actions.

Just in time with the Rails 2.3 release cells got tagged 2.3 compatible. Thanks to some (great!) major changes in the Rails API we were able to throw away a LOT lines of code, configuration and thus make cells faster, cleaner and even easier to use. Here’s a short overview.

Built-in Engines support Engines are now an official part of the Rails core - meaning we can put cells inside engines/plugins and use them in our application as if they would reside in the global app/ directory. Even better:

No more configuration The tiresome task of configuring Rails working with cells is obsolete. You don’t have to touch config/environment.rb anymore. Especially you no longer need the lines

require File.join(File.dirname(__FILE__), '../vendor/plugins/cells/boot')

I18N support Use translation features in your cell views like the t("...") helper to provide localized versions of your application.

Automatic template lookup caching Cell template lookups (the filepaths of templates) are now cached like controller view lookups, providing automatic reloading in development mode and caching in production by using rails’ ActionView::Paths.

State output caching You can apply fine-grained output caching for each cell state. As soon as you declare a state as cached in the cell class, it won’t be executed the next time you render it. To re-render you can define a devalidation proc.

cache :some_state
cache :some_state,  Proc.new {|cell| false if cell.too_old?}

Download cells

Grab the plugin at github and also check out the example app demonstrating how to plug cells into your app and how to use advanced features like caching states and localized cell views.

Major refactorings in Rails 2.3 provide some clever new hooks for plugins. We loved hitting the delete key, getting rid of cells code and having a better cells as a result. Thanks and props to the rails core team!