On Github johnotander / decorators_on_rails
https://github.com/johnotander/draper_example https://github.com/drapergem/draper http://johnotander.com/rails/2014/03/07/decorators-on-rails/
A design pattern that allows behavior to be added to an individual object without affecting the behavior of other objects from the same class.
Don't use Decorators, or Presenters, until you're in pain. Your models should be bursting at the seams.
The whole idea of logic in templates leads to all kinds of problems. They're hard to test, they're hard to read, and it's not just a slippery slope, but a steep one. Things go downhill rapidly.
<h1>Show user</h1> <dl class="dl-horizontal"> <% if @user.public_email %> <dt>Email:</dt> <dd><%= @user.email %></dd> <% else %> <dt>Email Unavailable:</dt> <dd><%= link_to 'Request Email', '#', class: 'btn btn-default btn-xs' %></dd> <% end %> <dt>Name:</dt> <dd> <% if @user.first_name || @user.last_name %> <%= "#{ @user.first_name } #{ @user.last_name }".strip %> <% else %> No name provided. <% end %> </dd> <dt>Joined:</dt> <dd><%= @user.created_at.strftime("%A, %B %e") %></dd> <!-- ... --> </dl>
class User < ActiveRecord::Base before_create :setup_analytics after_create :send_welcome_email after_save :send_confirmation_email, if: :email_changed? validates :website, url_format: true validates :email, email_format: true, presence: { message: "Please specify an email." }, uniqueness: { case_sensitive: false, message: "That email has already been registered." } validates_presence_of :password default_scope -> { order(:last_name, :first_name) } def full_name if first_name.blank? && last_name.blank? 'No name provided.' else "#{ first_name } #{ last_name }".strip end end def as_json(options = {}) json_blob = super json_blob.delete(:email) unless public_email json_blob end def email_domain email.split(/@/).second end def website_domain UrlFormat.get_domain(url) end private def send_confirmation_email # ... end def send_welcome_email # ... end def setup_analytics # ... end end
gem 'draper'
$ bundle install
$ rails generate decorator User
class UserDecorator < Draper::Decorator delegate_all end
require 'spec_helper' describe UserDecorator do let(:first_name) { 'John' } let(:last_name) { 'Smith' } let(:user) { FactoryGirl.build(:user, first_name: first_name, last_name: last_name) } let(:decorator) { user.decorate } describe '.full_name' do context 'without a first name' do before { user.first_name = '' } it 'should return the last name' do expect(decorator.full_name).to eq(last_name) end end context 'with a first and last name' do it 'should return the full name' do expect(decorator.full_name).to eq("#{ first_name } #{ last_name }") end end context 'without a first or last name' do before do user.first_name = '' user.last_name = '' end it 'should return no name provided' do expect(decorator.full_name).to eq('No name provided.') end end end end
class UserDecorator < Draper::Decorator delegate_all def email_or_request_button public_email ? email : h.link_to('Request Email', '#', class: 'btn btn-default btn-xs').html_safe end def full_name if first_name.blank? && last_name.blank? 'No name provided.' else "#{ first_name } #{ last_name }".strip end end def joined_at created_at.strftime("%B %Y") end end
class UsersController < ApplicationController before_action :do_stuff # GET /users # GET /users.json def index @users = User.all.decorate end # GET /users/1 # GET /users/1.json def show @user = User.find(params[:id]).decorate end end
<h1>Show user</h1> <dl class="dl-horizontal"> <% if @user.public_email %> <dt>Email:</dt> <dd><%= @user.email %></dd> <% else %> <dt>Email Unavailable:</dt> <dd><%= link_to 'Request Email', '#', class: 'btn btn-default btn-xs' %></dd> <% end %> <dt>Name:</dt> <dd> <% if @user.first_name || @user.last_name %> <%= "#{ @user.first_name } #{ @user.last_name }".strip %> <% else %> No name provided. <% end %> </dd> <dt>Joined:</dt> <dd><%= @user.created_at.strftime("%A, %B %e") %></dd> <!-- ... --> </dl>
<h1><%= @user.full_name %></h1> <dl class="dl-horizontal"> <dt><%= @user.email_attr_text %></dt> <dd><%= @user.email_or_request_button %></dd> <dt>Name:</dt> <dd><%= @user.full_name %></dd> <dt>Joined:</dt> <dd><%= @user.joined_at %></dd> <!-- ... --> </dl>
http://robots.thoughtbot.com/evaluating-alternative-decorator-implementations-in
http://www.confreaks.com/videos/884-railsconf2012-presenters-and-decorators-a-code-tour
http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
http://blog.steveklabnik.com/posts/2011-12-30-active-record-considered-harmful