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