README.md in chic-0.2.0 vs README.md in chic-0.3.0
- old
+ new
@@ -1,87 +1,355 @@
# Chic
-Opinionated presentation layer comprised of presenters and formatters.
+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
-Add this line to your application's Gemfile:
+To get started, add this line to your Gemfile and install it using Bundler:
- gem 'chic'
+```ruby
+gem 'chic'
+```
-And then execute:
+## Usage
- $ bundle
+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.
-Or install it yourself as:
+### Creating Presenters
- $ gem install chic
+Create a presenter by deriving from `Chic::Presenter`, and then declare which attributes should return formatters or presenters.
-## Usage
+```ruby
+# app/presenters/foo_presenter.rb
-### Being Presentable
+class FooPresenter < Chic::Presenter
+ formats :baz
+
+ presents bar: 'BarPresenter'
+end
+```
-Make objects easily presentable by:
+### Using Presenters
+Include `Chic::Presentable` to make your objects presentable:
+
```ruby
-class Foo
+# app/models/foo.rb
+
+class Foo < ApplicationRecord
include Chic::Presentable
+
+ belongs_to :bar
end
```
-### Creating Presenters
+Instantiate a presenter by calling `.present` on the presenter class, for example in a Rails view:
-Present presentables with a presenter by inheriting from `Chic::Presenter`:
+```erb
+<% FooPresenter.present @foo do |foo_presenter| %>
+ <!-- ... -->
+<% end %>
+```
+Collections can be presented using `.present_each`:
+
+```erb
+<% FooPresenter.present_each @foos do |foo_presenter, foo| %>
+ <!-- ... -->
+<% end %>
+```
+
+You can also include the view helpers:
+
```ruby
-class FooPresenter < Chic::Presenter
- # ...
+module ApplicationHelper
+ include Chic::Helpers::View
end
```
-You can also include `Chic::Presents` and `Chic::Formats` as needed:
+Which will allow you to instantiate presenters without having to use the class name:
+```erb
+<% present @foo do |foo_presenter| %>
+ <!-- ... -->
+<% end %>
+
+<% present_each @foos do |foo_presenter, foo| %>
+ <!-- ... -->
+<% end %>
+```
+
+See the [Conventions](#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:
+
```ruby
-class FooPresenter
- include Chic::Formats
- include Chic::Presents
- # ...
+# 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 need to make sure that the object being presented and the context in which it is being presented, for example the view, are accessible through `object` and `context` on the presenter instance respectively.
+**Note:** You should always return `nil` if the object being formatted is blank so that the `Nil` formatter behaves correctly.
-### Using Presenters
+Provide additional formatter options as chainable methods:
-Presenters should be instantiated from views using `.present`:
+```ruby
+# 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`:
+
```ruby
+# 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:
+
+```erb
<% FooPresenter.present @foo do |foo_presenter, _foo| %>
- <!-- ... -->
+ <%= foo_presenter.created_at %>
<% end %>
```
-Collections can be presented using `.present_each`:
+#### Configurable options
+If the formatter derives from `Chic::Formatters::Nil`, then configure a blank value to be used:
+
```ruby
-<% FooPresenter.present_each @foos do |foo_presenter, _foo| %>
- <!-- ... -->
+# 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:
+
+```ruby
+# 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:
+
+```erb
+<% FooPresenter.present @foo do |foo_presenter, _foo| %>
+ <%= foo_presenter.created_at.format('%c').blank_value('–') %>
<% end %>
```
-If you've made use of the view helpers, you can drop the class name:
+#### Named formatters
+Optionally configure formatters with a name, for example in a Rails initializer:
+
```ruby
-<% present @foo do |foo_presenter, _foo| %>
+# 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:
+
+```ruby
+# 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:
+
+```ruby
+# 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:
+
+```ruby
+# 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:
+
+```ruby
+# 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**
+
+```ruby
+# app/controllers/foo_controller.rb
+
+class FoosController < ApplicationController
+ def show
+ @foo = Foo.find(params[:id])
+ @foo_presenter = FooPresenter.new(@foo)
+ end
+end
+```
+
+```erb
+<!-- app/views/foos/_show.html.erb -->
+
+<%= link_to @foo_presenter.created_at, foo_path(@foo) %>
+```
+
+**Do**
+
+```ruby
+# app/controllers/foo_controller.rb
+
+class FoosController < ApplicationController
+ def show
+ @foo = Foo.find(params[:id])
+ end
+end
+```
+
+```erb
+<!-- 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**
+
+```erb
+<% present @foo do |foo_presenter| %>
<!-- ... -->
+ <%= render partial: 'path/to/partial', locals: { foo: foo_presenter } %>
<% end %>
```
-And:
+**Do**
-```ruby
-<% present_each @foo do |foo_presenter, _foo| %>
+```erb
+<% 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**
+
+```erb
+<% 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**
+
+```erb
+<% present_each @foos do |foo_presenter, foo| %>
+ <% if foo.created_at %>
+ <%= link_to foo_presenter.created_at, foo_path(foo) %>
+ <% end
<% end %>
```
## Development