Skip to content
/ chic Public

Opinionated presentation layer for Ruby applications using presenters and formatters.

License

Notifications You must be signed in to change notification settings

collcoll/chic

Repository files navigation

Chic

An opinionated presentation layer comprising presenters and formatters.

Chic was borne out of a need for a simple PORO-style presentation layer for Rails applications to help DRY up formatting logic in views and view helpers.

Installation

To get started, add this line to your Gemfile and install it using Bundler:

gem 'chic'

Usage

Though it is not a requirement, for ease of reference it is assumed that you will be using this library in a Rails application where you will be creating presenters for your models.

Creating Presenters

Create a presenter by deriving from Chic::Presenter, and then declare which attributes should return formatters or presenters.

# app/presenters/foo_presenter.rb

class FooPresenter < Chic::Presenter
  formats :baz
  
  presents bar: 'BarPresenter'
end

Using Presenters

Include Chic::Presentable to make your objects presentable:

# app/models/foo.rb

class Foo < ApplicationRecord
  include Chic::Presentable
  
  belongs_to :bar
end

Instantiate a presenter by calling .present on the presenter class, for example in a Rails view:

<% FooPresenter.present @foo do |foo_presenter| %>
  <!-- ... -->
<% end %>

Collections can be presented using .present_each:

<% FooPresenter.present_each @foos do |foo_presenter, foo| %>
  <!-- ... -->
<% end %>

You can also include the view helpers:

module ApplicationHelper
  include Chic::Helpers::View
end

Which will allow you to instantiate presenters without having to use the class name:

<% present @foo do |foo_presenter| %>
  <!-- ... -->
<% end %>

<% present_each @foos do |foo_presenter, foo| %>
  <!-- ... -->
<% end %>

See the Conventions section below for more on using presenters.

Creating Formatters

Formatters format values by deriving from Chic::Formatters::Nil and overriding the private value method:

# app/formatters/date_time_formatter.rb

class DateTimeFormatter < Chic::Formatters::Nil
  private

  def value
    return if object.blank?

    object.strftime('%-d %b %Y %H:%M')
  end
end

Note: You should always return nil if the object being formatted is blank so that the Nil formatter behaves correctly.

Provide additional formatter options as chainable methods:

# app/formatters/date_time_formatter.rb

class DateTimeFormatter < Chic::Formatters::Nil
  def format=(value)
    @format = value
    self
  end
  
  private

  def value
    return if object.blank?

    object.strftime(@format || '%-d %b %Y %H:%M')
  end
end

Using Formatters

Declare formatted values in presenters using formats:

# app/presenters/foo_presenter.rb

class FooPresenter < Chic::Presenter
  formats :created_at,
          with: 'DateTimeFormatter'
end

Render formatted values by calling #to_s on the formatter returned, which happens implicitly in Rails views for example:

<% FooPresenter.present @foo do |foo_presenter, _foo| %>
  <%= foo_presenter.created_at %>
<% end %>

Configurable options

If the formatter derives from Chic::Formatters::Nil, then configure a blank value to be used:

# app/presenters/foo_presenter.rb

class FooPresenter < Chic::Presenter
  formats :created_at,
          with: 'DateTimeFormatter',
          options: {
            blank_value: '(Not yet created)'
          }
end

If the formatter supports additional options using chainable methods as described above, configure those options in the same way:

# app/presenters/foo_presenter.rb

class FooPresenter < Chic::Presenter
  formats :created_at,
          with: 'DateTimeFormatter',
          options: {
            format: '%-d %B %Y at %H:%M'
          }
end

If needed, override those options where the formatted value is being rendered:

<% FooPresenter.present @foo do |foo_presenter, _foo| %>
  <%= foo_presenter.created_at.format('%c').blank_value('–') %>
<% end %>

Named formatters

Optionally configure formatters with a name, for example in a Rails initializer:

# config/initializers/chic.rb

require 'chic'

Chic.configure do |config|
  config.formatters.merge!(
    date_time: 'DateTimeFormatter'
  )
end

Allowing you to refer to those formatters by name instead of by class:

# app/presenters/foo_presenter.rb

class FooPresenter < Chic::Presenter
  formats :created_at,
          with: :date_time
end

Logging

If a presenter class for an object you're trying to present can't be found, an entry at debug level will be made to the configured logger.

You can configure the logger to be used:

# config/initializers/chic.rb

require 'chic'

Chic.configure do |config|
  config.logger = Logger.new($stdout)
end

It may be beneficial to know about missing presenter classes sooner than later, in which case you can enable exceptions when it makes sense to do so – for example, a Rails application in any environment other than production:

# config/initializers/chic.rb

require 'chic'

Chic.configure do |config|
  config.raise_exceptions = Rails.env.production? == false
end

Conventions

A few helpful conventions that have gone a long way to keep things maintainable.

Naming presenter classes

Presenter class names are derived by appending Presenter to the #model_name or the class name of the object being presented. It is strongly recommended that you stick to this convention, but if you need to change it – for example you might have overridden #model_name – you can do so by defining a #presenter_class method:

# app/forms/user/sign_up_form.rb

class User::SignUpForm < User
  include Chic::Presentable
  
  def self.model_name
    ActiveModel::Name.new(self, nil, 'User')
  end

  def presenter_class
    User::SignUpFormPresenter
  end
end

Instantiate presenters in views only

Try not instantiate presenters outside of the view layer if possible.

Don't

# app/controllers/foo_controller.rb

class FoosController < ApplicationController
  def show
    @foo = Foo.find(params[:id])
    @foo_presenter = FooPresenter.new(@foo)
  end
end
<!-- app/views/foos/_show.html.erb -->

<%= link_to @foo_presenter.created_at, foo_path(@foo) %>

Do

# app/controllers/foo_controller.rb

class FoosController < ApplicationController
  def show
    @foo = Foo.find(params[:id])
  end
end
<!-- app/views/foos/_show.html.erb -->

<% present @foo do |foo_presenter| %>
  <%= link_to foo_presenter.created_at, foo_path(@foo) %>
<% end %>

Keep presenter instances scoped to only the view in which they're being used

It can get messy if you pass presenter instances to other views, try avoid that if possible.

Don't

<% present @foo do |foo_presenter| %>
  <!-- ... -->
  <%= render partial: 'path/to/partial', locals: { foo: foo_presenter } %>
<% end %>

Do

<% present @foo do |foo_presenter| %>
  <!-- ... -->
  <%= render partial: 'path/to/partial', locals: { foo: @foo } %>
<% end %>

Use presenters and formatters for presentation only

It may be tempting to use presenters for conditional logic, but it's far better to use the original object for anything other than presentation.

Don't

<% present_each @foos do |foo_presenter, foo| %>
  <% if foo_presenter.created_at %>
    <%= link_to foo_presenter.created_at, foo_path(foo_presenter) %>
  <% end
<% end %>

Note: Passing a presenter instance to a route helper as shown would only work if the presenter declared id as a formatted attribute. While this may work, it is not predictable behaviour.

Do

<% present_each @foos do |foo_presenter, foo| %>
  <% if foo.created_at %>
    <%= link_to foo_presenter.created_at, foo_path(foo) %>
  <% end
<% end %>

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/collcoll/chic. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Chic project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

About

Opinionated presentation layer for Ruby applications using presenters and formatters.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published