Upgrade to Pro — share decks privately, control downloads, hide ads and more …

DRYing up your views

DRYing up your views

A talk about view logic I gave at the Ruby en Rails 2009 conference in Amsterdam on the 30th of October that year.

Dax Huiberts

October 30, 2009
Tweet

More Decks by Dax Huiberts

Other Decks in Programming

Transcript

  1. Some basics • Keep your logic out of your views

    • HTML-escape your user output • Don’t mix javascript inside your views • Use partials for recurring HTML structures
  2. Keep your logic out of your views Don’t do this:

    <% main_categories = Category.find :all, :conditions => "parent_id IS NULL" %>
  3. Keep your logic out of your views Don’t do this:

    <% main_categories = Category.find :all, :conditions => "parent_id IS NULL" %> Better: <% main_categories = Category.main %>
  4. Keep your logic out of your views Don’t do this:

    <% main_categories = Category.find :all, :conditions => "parent_id IS NULL" %> Better: <% main_categories = Category.main %> Best: def main_categories @main_categories ||= Category.main end
  5. HTML-escape your user output Hi, my name is: <script>window.location='http://bit.ly/4kb77v'</script> <%=

    link_to @user.name, @user %> Results in: <a href="/users/666"><script>window.location='http://bit.ly/4kb77v'</ script></a> SHIT!!!
  6. HTML-escape your user output Hi, my name is: <script>window.location='http://bit.ly/4kb77v'</script> <%=

    link_to h(@user.name), @user %> Results in: <a href="/users/666">&lt;script&gt;window.location='http://bit.ly/4kb77v'&lt;/ script&gt;</a> GOTCHA!!!
  7. Don’t mix javascript inside your views <a href="/users/666" class="delete">&lt;script&gt;window.location='http://bit.ly/4kb77v'&lt;/ script&gt;</a>

    is much better than: <a onclick="var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);f.submit();return false;" href="/users/ 666">&lt;script&gt;window.location='http://bit.ly/4kb77v'&lt;/script&gt;</ a>
  8. Use partials for recurring HTML structures Your view: <div class="tl">

    <div class="tr"> <div class="bl"> <div class="br"> <%= @post.body %> </div> </div> </div> </div>
  9. Use partials for recurring HTML structures Your PARTIAL: <div class="tl">

    <div class="tr"> <div class="bl"> <div class="br"> <%= data %> </div> </div> </div> </div>
  10. Use partials for recurring HTML structures Your PARTIAL: <div class="tl">

    <div class="tr"> <div class="bl"> <div class="br"> <%= data %> </div> </div> </div> </div> Calling it with: <%= render :partial => "shared/rounded_box", :locals => {:data => @post.body} %>
  11. Use partials for recurring HTML structures Your helper method: def

    rounded_box &block data = capture(&block) concat(render(:partial => "shared/rounded_box", :locals => {:data => concat()})) end
  12. Use partials for recurring HTML structures Your helper method: def

    rounded_box &block data = capture(&block) concat(render(:partial => "shared/rounded_box", :locals => {:data => concat()})) end And your view: <% rounded_box do %> <%= @post.body %> <% end %>
  13. Haml • Started using it in summer 2007 • Hate

    it => get used to it => loved it • Closer to the CSS domain • Markup should be beautiful • http://haml-lang.com/
  14. Haml ERB: <div id="profile"> <div class="left column"> <div id="date"><%= print_date

    %></div> <div id="address"><%= current_user.address %></div> </div> <div class="right column"> <div id="email"><%= current_user.email %></div> <div id="bio"><%= current_user.bio %></div> </div> </div> UGLY!!!
  15. Haml Haml: %strong{:class => "code", :id => "message"} Hello, World!

    ERB: <strong class="code" id="message">Hello, World!</strong>
  16. Formtastic • Forms are friggin’ boring to code • Semantically

    rich & accessible • Automatic support for: • Labeling • Error reporting • Extensive markup possibilities • http://github.com/justinfrench/formtastic
  17. Formtastic form_for: <% form_for @user do |form| %> <div> <%=

    form.label :title %> <%= form.text_field :title %> </div> <div> <%= form.label :body %> <%= form.text_area :body %> </div> <div> <%= submit_tag "Save" %> </div> <% end %> EWWW!!!
  18. Formtastic semantic_form_for: <% semantic_form_for @user do |form| %> <%= form.inputs

    :title, :body %> <%= form.buttons :commit %> <% end %> YAY!!!
  19. Formtastic semantic_form_for with Haml: - semantic_form_for @user do |form| =

    form.inputs :title, :body = form.buttons :commit WHOOHOO!!!
  20. Block helpers • When partials start to contain to much

    options • When partials look more like ruby than HTML • http://github.com/markevans/block_helpers
  21. Block helpers Calling a partial: <%= render :partial => "videos/video",

    :locals => {:video => video, :hide_description => true, :style_tag => :li }%>
  22. Block helpers Complex partial: <% hide_thumbnail ||= true hide_description ||=

    true header_tag ||= :h2 style_tag ||= :div css_class ||= 'video' %> <% content_tag style_tag, :class => css_class do %> <%= content_tag header_tag, video.title %> <%= image_tag(video.thumbnail_url, :class => 'thumb') unless hide_thumbnail %> <%= '<p>%s</p>' % h(video.description) unless hide_description %> <% end %> Hmmm.....
  23. Block helpers Block helper (part 1 of 2): module VideosHelper

    class VideoBlock < BlockHelpers::Base DEFAULT_OPTIONS = {:hide_thumbnail => true, :hide_description => true, :style_tag => :div, :header_tag => :h2, :css_class => "video"} attr_accessor :video, :options def initialize(video, options = {}) @video = video @options = DEFAULT_OPTIONS.merge(options) end ...
  24. Block helpers Block helper (part 2 of 2): ... def

    display header = content_tag(options[:header_tag], video.title) thumbnail = image_tag(video.thumbnail, :class => "thumb") unless options[:hide_thumbnail] description = content_tag(:p, video.description) unless options[:hide_description] data = header + thumbnail.to_s + description.to_s content_tag(options[:style_tag], :class => options[:css_class], data) end end end
  25. Block helpers summary • Shift from ERB to Ruby •

    Don’t pretend it’s a partial anymore • More structured & more maintainable over time
  26. Presenters • Apply the adapter (wrapper) pattern to combine controller

    logic with domain logic • Keep logic in it’s rightful place. • Prevent models to contain controller logic • Warning: It’s still a concept!
  27. Presenters Wrong ERB: <% if current_user && (current_user.is_admin? || (@topic.creator

    == current_user && [email protected])) %> <%= link_to "Edit topic", @topic %> <% end %> WRONG!!!
  28. Presenters Presenter file: class TopicPresenter < Presenter::Base def can_edit? is_admin?

    || (is_owner? && !locked) end def can_lock? is_admin? end def can_reply? current_user && !locked end ... ... def is_owner? creator == current_user end private def is_admin? current_user && current_user.is_admin? end end
  29. Presenters summary • Like I said, it’s still a concept

    • Can clean up complex partials a lot • Keeps models the way they should be • More info at: http://www.htmltimes.com/presenters-in- Ruby-on-Rails-applications.php or http://bit.ly/vUlv
  30. Conclusion • It saved precious bits on your hard drive

    • Higher level of abstraction • Views get more readable, thus better maintainable • Beware: Big shift from ERB/Haml to Ruby • But, It’s a joy writing views again!