#287 Presenters from Scratch pro
Oct 03, 2011 | 14 minutes | Views, Refactoring
Clean up complex view logic with the help of presenters, and doing this from scratch gives you a lot of flexibility. Here I show not only how to create presenters, but how to test them using Test Unit and RSpec.
- Download:
- source codeProject Files in Zip (131 KB)
- mp4Full Size H.264 Video (30.2 MB)
- m4vSmaller H.264 Video (16.7 MB)
- webmFull Size VP8 Video (19.1 MB)
- ogvFull Size Theora Video (34.7 MB)
Resources
Note: In the episode I reversed the parameters in the ApplicationController#present
method. See below for the fixed code.
app/presenters/user_presenter.rb
class UserPresenter < BasePresenter presents :user delegate :username, to: :user def avatar site_link image_tag("avatars/#{avatar_name}", class: "avatar") end def linked_name site_link(user.full_name.present? ? user.full_name : user.username) end def member_since user.created_at.strftime("%B %e, %Y") end def website handle_none user.url do h.link_to(user.url, user.url) end end def twitter handle_none user.twitter_name do h.link_to user.twitter_name, "http://twitter.com/#{user.twitter_name}" end end def bio handle_none user.bio do markdown(user.bio) end end private def handle_none(value) if value.present? yield else h.content_tag :span, "None given", class: "none" end end def site_link(content) h.link_to_if(user.url.present?, content, user.url) end def avatar_name if user.avatar_image_name.present? user.avatar_image_name else "default.png" end end end
app/presenters/base_presenter.rb
class BasePresenter def initialize(object, template) @object = object @template = template end private def self.presents(name) define_method(name) do @object end end def h @template end def markdown(text) Redcarpet.new(text, :hard_wrap, :filter_html, :autolink).to_html.html_safe end def method_missing(*args, &block) @template.send(*args, &block) end end
application_helper.rb
module ApplicationHelper def present(object, klass = nil) klass ||= "#{object.class}Presenter".constantize presenter = klass.new(object, self) yield presenter if block_given? presenter end end
application_controller.rb
def present(object, klass = nil) klass ||= "#{object.class}Presenter".constantize klass.new(object, view_context) end
app/views/users/show.html.erb
<% present @user do |user_presenter| %> <div id="profile"> <%= user_presenter.avatar %> <h1><%= user_presenter.linked_name %></h1> <dl> <dt>Username:</dt> <dd><%= user_presenter.username %></dd> <dt>Member Since:</dt> <dd><%= user_presenter.member_since %></dd> <dt>Website URL:</dt> <dd><%= user_presenter.website %></dd> <dt>Twitter Name:</dt> <dd><%= user_presenter.twitter %></dd> <dt>Bio:</dt> <dd><%= user_presenter.bio %></dd> </dl> </div> <% end %>
test/unit/presenters/user_presenter_test.rb
class UserPresenterTest < ActionView::TestCase test "says when none given" do presenter = UserPresenter.new(User.new, view) assert_match "None given", presenter.website end end
spec/presenters/user_presenter_spec.rb
describe UserPresenter do it "says when none given" do presenter = UserPresenter.new(User.new, view) presenter.website.should include("None given") end end
spec/spec_helper.rb
RSpec.configure do |config| config.include ActionView::TestCase::Behavior, example_group: {file_path: %r{spec/presenters}} end