Advanced Views with Erector
      A builder view framework
             Jeff Dean
ERB views kinda suck
Make you work more
No encapsulation
Refactoring can be hard
We need something that...
Requires less work
Respects encapsulation
Is testable in isolation
Erector to the rescue!
Views are classes
Views are classes

• Modular decomposition
• Inheritance (nested layouts)
• Consistent semantics
Views are classes
class Views::Articles::Show < Erector::RailsWidget

  def content
    div :class => "content" do
      p "Hello <script> World!", :class => "sidebar"
    end
  end

end
Does the right thing
Does the right thing

• Escapes HTML
• Auto-closes tags
• You control indenting and whitespace
Does the right thing
class Views::Articles::Show < Erector::RailsWidget
  def content
    div :class => "content" do
      p "Hello <script> World!", :class => "sidebar"
    end
  end
end

# <div class="content">
#   <p class="sidebar">Hello &lt;script&gt; World!</p>
# </div>
ERB Refactoring
<!-- app/views/articles/show.html.erb -->

<%=h truncate(article.title, :length => 10) %>
<!-- app/views/articles/show.html.erb -->

<%=h display_name(article) %>




# app/helpers/articles.rb

def display_name(article)
  truncate(article.title, :length => 10)
end
<!-- app/views/articles/show.html.erb -->

<%=h article_display_name(article) %>




# app/helpers/articles.rb

def article_display_name(article)
  truncate(article.title, :length => 10)
end
<!-- app/views/articles/show.html.erb -->

<%=h article_display_name(article) %>




# app/helpers/articles.rb

def article_display_name(article)
  truncate(article.title, :length => 10)
end
<!-- app/views/articles/show.html.erb -->

<%= article_display_name(article) %>




# app/helpers/articles.rb

def article_display_name(article)
  content_tag :span,
    h(truncate(article.title, :length => 10)),
    :title => article.title
end
<!-- app/views/articles/show.html.erb -->

<%= article_display_name(article) %>




# app/helpers/articles.rb

def article_display_name(article)
  content_tag :span,
    h(truncate(article.title, :length => 10)),
    :title => article.title
end
<!-- app/views/articles/show.html.erb -->

<%= article_display_name(article) %>




# app/helpers/articles.rb

def article_display_name(article)
  content_tag :span,
    h(truncate(article.title, :length => 10)),
    :title => article.title
end
<!-- app/views/articles/show.html.erb -->

<%= render("articles/title", :title => article) %>




<!-- app/views/_title.html.erb -->

<span title="<%= article.title %>">
  <%=h truncate(article.title, :length => 10) %>
</span>
<!-- app/views/articles/show.html.erb -->

<%= render("articles/title", :title => article) %>




<!-- app/views/_title.html.erb -->

<span title="<%= article.title %>">
  <%=h truncate(article.title, :length => 10) %>
</span>
Erector Refactoring
# app/views/articles/show.rb

class Views::Articles::Show < Erector::RailsWidget
  def content
    text do
      truncate @article.title, :length => 10
    end
  end
end
# app/views/articles/show.rb

class Views::Articles::Show < Erector::RailsWidget
  def content
    display_name
  end

  private
    def display_name
      text do
        truncate @article.title, :length => 10
      end
    end
end
# app/views/articles/show.rb

class Views::Articles::Show < Erector::RailsWidget
  def content
    display_name
  end

  private
    def display_name
      text do
        truncate @article.title, :length => 10
      end
    end
end
# app/views/articles/show.rb

class Views::Articles::Show < Erector::RailsWidget
  def content
    display_name
  end

  private
    def display_name
      span :title => @article.title do
        truncate @article.title, :length => 10
      end
    end
end
# app/views/articles/show.rb

class Views::Articles::Show < Erector::RailsWidget
  def content
    display_name
  end

  private
    def display_name
      span :title => @article.title do
        truncate @article.title, :length => 10
      end
    end
end
# app/views/articles/show.rb

class Views::Articles::Show < Views::Articles::Base
  def content
    display_name
  end
end

# app/views/articles/base.rb

class Views::Articles::Base < Erector::RailsWidget
  def display_name
    span :title => @article.title do
      truncate @article.title, :length => 10
    end
  end
end
# app/views/articles/show.rb

class Views::Articles::Show < Views::Articles::Base
  def content
    display_name
  end
end

# app/views/articles/base.rb

class Views::Articles::Base < Erector::RailsWidget
  def display_name
    span :title => @article.title do
      truncate @article.title, :length => 10
    end
  end
end
# app/views/articles/show.rb

class Views::Articles::Show < Views::Articles::Base
  def content
    display_name
  end
end

# app/views/articles/base.rb

class Views::Articles::Base < Erector::RailsWidget
  def display_name
    span :title => @article.title do
      truncate @article.title, :length => 10
    end
  end
end
What’s next?
more rails helpers
better performance
rails 3
erector.rubyforge.org

github.com/pivotal/erector

 jdean@pivotallabs.com
Questions

Presentation.Key

Editor's Notes

  • #4 You have to manage indenting and whitespace yourself You have to close the tags yourself You have to decide when to html escape Poor IDE support Proper factoring makes code slow - render partial
  • #5 Helpers are typically available in all views No namespacing Instance variables are psuedo-global Yield has inconsistent semantics Concat binding fuckupedness The way variables are bound affects refactoring
  • #6 Code lives in different files - view, layout, partial and the helpers module You have to know when to use auto-naming, instance variables, locals Inconsistent calling conventions for helpers View specs can be hard # helpers returning raw text [ :image_tag, :javascript_include_tag, :stylesheet_link_tag, :sortable_element, :sortable_element_js, :text_field_with_auto_complete, # helpers returning raw text whose first parameter is HTML escaped [ :link_to, :link_to_remote, :mail_to, :button_to, :submit_tag, # return text, take block [ :link_to, :link_to_remote, :link_to_function, :text_field_tag, :password_field_tag, :check_box_tag # render text, take block [ :error_messages_for, :form_tag, :form_for,
  • #8 Auto-escape html Manage whitespace and indentation for you Auto-close your tags Good IDE support Consistent semantics Little magic
  • #9 Methods, variables etc... How many of you are object oriented programmers? Functional gangbang
  • #10 Unit testability proves/enables good design Enables refactoring Enables you to test-drive Dependency injection
  • #12 &quot;Dependency injection&quot; via constructor params (aka &quot;Complete Construction&quot;) Well-defined semantics for variables, loops, blocks
  • #13 &quot;Dependency injection&quot; via constructor params (aka &quot;Complete Construction&quot;) Well-defined semantics for variables, loops, blocks
  • #14 &quot;Dependency injection&quot; via constructor params (aka &quot;Complete Construction&quot;) Well-defined semantics for variables, loops, blocks
  • #15 pretty printing
  • #16 pretty printing
  • #17 pretty printing
  • #22 Rename has to go into both files Probably have to rename this since you probably have all helpers included by default
  • #24 You have to decide where to h (this might change in rails 3)
  • #25 Different semantics