Decorator pattern with Draper for view logic

Sarah Mitchell Feb 2026
2 tabs
# Gemfile
gem 'draper'

# app/decorators/user_decorator.rb
class UserDecorator < Draper::Decorator
  delegate_all

  def full_name_with_role
    "#{object.name} (#{object.role.titleize})"
  end

  def avatar_image
    if object.avatar_url.present?
      h.image_tag(object.avatar_url, class: 'avatar')
    else
      h.image_tag('default-avatar.png', class: 'avatar')
    end
  end

  def formatted_join_date
    object.created_at.strftime("%B %d, %Y")
  end

  def member_since
    "Member since #{formatted_join_date}"
  end

  def status_badge
    badge_class = {
      'active' => 'badge-success',
      'inactive' => 'badge-secondary',
      'banned' => 'badge-danger'
    }[object.status]

    h.content_tag(:span, object.status.titleize, class: "badge #{badge_class}")
  end

  def posts_count_text
    count = object.posts.count
    "#{count} #{count == 1 ? 'post' : 'posts'}"
  end

  def edit_link
    return unless h.policy(object).update?

    h.link_to 'Edit Profile', h.edit_user_path(object), class: 'btn btn-primary'
  end

  def social_links
    links = []
    links << twitter_link if object.twitter_handle.present?
    links << github_link if object.github_username.present?
    h.safe_join(links, ' ')
  end

  private

  def twitter_link
    h.link_to "@#{object.twitter_handle}",
      "https://twitter.com/#{object.twitter_handle}",
      target: '_blank',
      class: 'social-link'
  end

  def github_link
    h.link_to object.github_username,
      "https://github.com/#{object.github_username}",
      target: '_blank',
      class: 'social-link'
  end
end

# Controller usage
class UsersController < ApplicationController
  def show
    user = User.find(params[:id])
    @user = user.decorate
    # Or: @user = UserDecorator.new(user)
  end

  def index
    users = User.page(params[:page])
    @users = users.decorate
    # Decorates entire collection
  end
end

# View usage
<%= @user.avatar_image %>
<h1><%= @user.full_name_with_role %></h1>
<p><%= @user.member_since %></p>
<%= @user.status_badge %>
<p><%= @user.posts_count_text %></p>
<%= @user.edit_link %>
<div class="social">
  <%= @user.social_links %>
</div>
2 files · ruby Explain with highlit

Draper decorators encapsulate view-specific logic, keeping models clean. Decorators wrap models, adding presentation methods without polluting domain logic. I use decorators for formatting, conditional rendering, helper delegation. Decorators access helper methods via h or helpers. They're object-oriented alternative to procedural helpers. Decorating collections applies decorator to each element. Decorators compose—one decorator can delegate to another. Testing decorators is straightforward—no controller/request setup needed. Draper follows Presenter pattern, improving separation of concerns. Understanding when to use decorators vs. helpers vs. view models is key. Decorators shine for rich, contextual presentation logic tied to specific models.