No release in over 3 years
Low commit activity in last 3 years
Uber simple presenters
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

 Project Readme

Resubject

CI Gem Version

Simple presenters built on Ruby's SimpleDelegator. Wrap your objects with presenter classes that add display logic while keeping your models clean.

Installation

Add to your Gemfile:

gem 'resubject', '~> 1.0'

Requires Ruby >= 3.1 and ActiveSupport >= 7.2.

Usage

Create a presenter by inheriting from Resubject::Presenter. All methods from the original object are available through delegation:

class Box < Struct.new(:name, :items)
end

class BoxPresenter < Resubject::Presenter
  def contents
    items.join(', ')
  end
end
box = Box.new('Awkward Package', ['platypus', 'sloth', 'anteater'])

presenter = BoxPresenter.new(box)
presenter.name     # => "Awkward Package" (delegated)
presenter.contents # => "platypus, sloth, anteater"

Collections

Wrap a collection of objects with Presenter.all:

boxes = [box1, box2, box3]
BoxPresenter.all(boxes)
# => [#<BoxPresenter>, #<BoxPresenter>, #<BoxPresenter>]

Nested presenters

Use presents to automatically wrap associated objects:

class PostPresenter < Resubject::Presenter
  presents :author                         # infers AuthorPresenter
  presents :comments, CommentPresenter     # explicit presenter class
end

post = PostPresenter.new(post)
post.author   # => #<AuthorPresenter>
post.comments # => [#<CommentPresenter>, ...]

Or use present inside a method for more control:

class PostPresenter < Resubject::Presenter
  def comments
    present(to_model.comments, CommentPresenter)
  end
end

Rails integration

Resubject includes a present helper in controllers and views automatically:

class BoxesController < ApplicationController
  def index
    @boxes = present(Box.all)
  end

  def show
    @box = present(Box.find(params[:id]))
  end
end

The helper resolves the presenter class by convention (Box -> BoxPresenter). You can also specify the presenter explicitly:

@boxes = present(Box.all, SpecialBoxPresenter)

Or stack multiple presenters:

@boxes = present(Box.all, BoxPresenter, ExtendedBoxPresenter)

Use present directly in views:

<%= present(@box).contents %>

ActionView helpers

Generate methods that delegate to ActionView helpers:

class ProductPresenter < Resubject::Presenter
  currency :price, precision: 2
  percentage :discount
  time_ago :published_at
  date_format :created_at, :short
end
product = ProductPresenter.new(product, view_context)
product.price        # => "$10.00"
product.discount     # => "25.000%"
product.published_at # => "about 1 hour"
product.created_at   # => "25 Dec 14:35"
Method Maps to Source
currency number_to_currency ActionView::Helpers::NumberHelper
percentage number_to_percentage ActionView::Helpers::NumberHelper
time_ago time_ago_in_words ActionView::Helpers::DateHelper
date_format to_fs ActiveSupport::TimeWithZone

Without Rails

Pass a template context manually when not using Rails:

require 'action_view'

presenter = PostPresenter.new(post, ActionView::Base.empty)

Testing

Resubject provides RSpec helpers that set up subject and template for specs in spec/presenters/:

# spec/presenters/user_presenter_spec.rb
require 'resubject/rspec'

describe UserPresenter do
  let(:object) { double(:user, first: 'Jane', last: 'Doe') }

  it 'has full name' do
    expect(subject.name).to eq 'Jane Doe'
  end
end

The subject is automatically created as described_class.new(object, template).

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes using conventional commits
  4. Push to the branch (git push origin my-new-feature)
  5. Create a Pull Request

License

MIT