Skip to content

Instantly share code, notes, and snippets.

@nicolas-brousse
Last active February 15, 2019 13:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicolas-brousse/9086ab16153bae3841f05c67442f9d28 to your computer and use it in GitHub Desktop.
Save nicolas-brousse/9086ab16153bae3841f05c67442f9d28 to your computer and use it in GitHub Desktop.
[WIP] A simple decorator for Rails

Usage

🚀 I create the dekorator gem if this interrest you.

Get started

Copy ApplicationDecorator inside app/decorators/application_decorator.rb and DecoratorsHelper inside decorators_helper.rb.

Then you could create and use your own decorators by create new class that extend from ApplicationDecorator.

Decorator

class PostDecorator < ApplicationDecorator
  include ActionView::Helpers::TextHelper

  decorates_association :comments
  # OR
  # decorates_association :comments, with: CommentDecorator

  def title
    title.upcase_first
  end

  def summary
    truncate(body, length: 170)
  end
end

class CommentDecorator < ApplicationDecorator
  def commented_at
    "commented at #{l(created_at)}"
  end
end

Views

To decorate a model, you have to use ApplicationDecorator#decorate. This method is accessible in your view with #decorate.

decorated_post = decorate(@post)
decorated_post = decorate(@post, with: PostDecorator)
decorate(@post) do |decorated_post|
  # use decorated_post here
end
<% @post = decorate(@post) %>
<h1><%= @post.title %></h1>

<p><%= @post.summary %></p>

<% @post.comments.each do |comment| %>
<p><b><%= comment.commented_at %></b></p>
<% end %>
# frozen_string_literal: true
class DecoratorNotFound < ArgumentError; end
class ApplicationDecorator < SimpleDelegator
class << self
def decorate(object_or_collection, with: nil)
return object_or_collection if object_or_collection.blank?
with = _guess_decorator(object_or_collection) if with.nil? && object_or_collection.present?
raise DecoratorNotFound, "Can't guess decorator for #{object_or_collection.class.name} object" if with.nil?
object_or_collection = _decorate(object_or_collection, with: with)
if block_given?
yield object_or_collection
else
object_or_collection
end
end
def decorates_association(relation_name, with: nil)
relation_name = relation_name.to_sym
define_method(relation_name) do
association = __getobj__.public_send(relation_name)
@decorated_associations[relation_name] = decorate(association, with: with)
end
end
def base_class
name.gsub("Decorator", "").safe_constantize
end
private
def _decorate(object_or_enumerable, with: nil)
case object_or_enumerable
when ActiveRecord::Relation
DecoratedEnumerableProxy.new(object_or_enumerable, with)
when Enumerable
object_or_enumerable.map { |object| with.new(object) }
else
with.new(object_or_enumerable)
end
end
def _guess_decorator(object_or_enumerable)
object_or_enumerable = object_or_enumerable.first if object_or_enumerable.is_a? Enumerable
"#{object_or_enumerable.class}Decorator".safe_constantize if object_or_enumerable.present?
end
end
delegate :decorate, to: :class
def initialize(object)
@decorated_associations = {}
super(object)
end
def object
__getobj__
end
class DecoratedEnumerableProxy < DelegateClass(ActiveRecord::Relation)
include Enumerable
delegate :as_json, :collect, :map, :each, :[], :all?, :include?,
:first, :last, :shift, to: :decorated_collection
delegate :each, to: :to_ary
def initialize(collection, decorator_class)
super(collection)
@decorator_class = decorator_class
end
def wrapped_collection
__getobj__
end
def decorated_collection
@decorated_collection ||= wrapped_collection.collect { |member| @decorator_class.decorate(member) }
end
alias to_ary decorated_collection
end
end
# frozen_string_literal: true
module DecoratorsHelper
delegate :decorate, to: "ApplicationDecorator"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment