felipeelias/resubject

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# Resubject

[![Build Status](https://travis-ci.org/felipeelias/resubject.svg?branch=master)](https://travis-ci.org/felipeelias/resubject)
[![Gem Version](https://badge.fury.io/rb/resubject.svg)](https://badge.fury.io/rb/resubject)
[![Code Climate](https://codeclimate.com/github/felipeelias/resubject/badges/gpa.svg)](https://codeclimate.com/github/felipeelias/resubject)

Uber simple presenters using Ruby's SimpleDelegator.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'resubject', '~> 0.3.0'
```

And then execute:

    $ bundle

## Ruby/Rails versions

This gem is compatible with Ruby 1.9 and 2.0 and Rails 3 and 4.

## Documentation

Checkout the documentation in [rdoc.info/resubject](http://rdoc.info/github/felipeelias/resubject/master/frames)

## Introduction

Resubject uses Ruby's [SimpleDelegator](http://apidock.com/ruby/SimpleDelegator) class to create its presenters.

SimpleDelegator is a concrete implementation of the Delegator class. Basically, it delegates any method calls to the object passed into the constructor:

```ruby
require 'delegate'

array = SimpleDelegator.new([1, 2, 3])

array.count # => 3
array.map   # => #<Enumerator: ...>
```

It doesn't override the class name, but you still can access the original object.

```ruby
array.class
# => SimpleDelegator
array.__getobj__.class
# => Array
```

This means you can create a class that inherits from SimpleDelegator and customize its behaviour:

```ruby
class ForeverZeroArray < SimpleDelegator
  def omg!
    "OMG!"
  end

  def count
    0
  end
end
```

You can define new methods or override existing ones:

```ruby
ForeverZeroArray.new([1,2,3]).count
# => 0
ForeverZeroArray.new([1,2,3]).omg!
# => OMG!
```

## Usage

Using the `Resubject::Presenter` class, you can create Presenters from it. Example:

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

class BoxPresenter < Resubject::Presenter
  def contents
    items.join(', ')
  end
end
```

Then use the delegator:

```ruby
box = Box.new('Awkward Package', ['platypus', 'sloth', 'anteater'])

presentable = BoxPresenter.new(box)
presentable.contents
# => platypus, sloth, anteater
```

If you have a collection of objects and want to add a presenter to each one, use `Resubject.all`

```ruby
boxes = [box1, box2, box3]
BoxPresenter.all boxes
# => [<BoxPresenter>, <BoxPresenter>, <BoxPresenter>]
```

## Rails

If you're using rails, Resubject automatically includes helpers in your controllers and views

```ruby
class BoxesController < ActionController::Base
  def index
    @boxes = present Boxes.all
  end
end
```

The `#present` method will automatically identify the presenter class by the object's class name. If you want to customize the class:

```ruby
def index
  @boxes = present Boxes.all, SpecialBoxPresenter
end
```

It also accepts multiple presenters:

```ruby
def index
  @boxes = present Boxes.all, BoxPresenter, ExtendedBoxPresenter
end
```

Or if you prefer, you can use the `#present` method directly into your views

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

## Helpers

You can define presentable attributes:

```ruby
class PostPresenter < Resubject::Presenter
  presents :title
  presents :comments # or => presents :comments, CommentPresenter
end
```

Then the attributes will return an instance of those presenters:

```ruby
post.title
# => <TitlePresenter>

post.comments
# => [<CommentPresenter>, <CommentPresenter>, <CommentPresenter>]
```

Or if you wish, you can use the `present` method inside your class

```ruby
class PostPresenter < Resubject::Presenter
  def comments
    present(to_model.comments, SomePresenter)
  end
end
```

### Helpers on Rails

`Resubject` can generate some rails helpers for your attributes:

```ruby
class ProductPresenter < Resubject::Presenter
  currency :price, precision: 2
end
```

Will generate:

```ruby
product.price
# => $10.00
```

#### Available helpers

```text
Presenter           Maps to                     Class/Module

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_s                        ActiveSupport::TimeWithZone
```

More helpers will be added. Feel free to contribute with yours! Also, Check out [the extensions file](https://github.com/felipeelias/resubject/blob/master/lib/resubject/extensions/template_methods.rb).

### Helpers without rails

If you'd like to use the generated helpers but you're not using Rails, you can either use `ActionView` or create your own template handler:

```ruby
require 'action_view'

post = PostPresenter.new(post, ActionView::Base.new)
# Or
post = PostPresenter.new(post, MyTemplateHandler.new)
```

If you want to use your own template handler and still use Resubject helpers, you may want to define the same `ActionView` helpers in your handler (or only the ones you will actually use).

## Testing

Resubject introduces a new context for RSpec that helps testing your presenters:

```ruby
# spec/presenters/my_presenter_spec.rb
# require 'action_view' # require this first if you want to test action view helpers
require 'resubject/rspec'
require 'presenters/my_presenter'

describe UserPresenter do
  let(:object) { double :user }

  it 'has full name' do
    object.stub(first: 'User', last: 'Name')
    expect(subject.name).to eq 'User Name'
  end
end
```

By placing the file into `spec/presenters`, Resubject automatically includes the `subject` and `template` variables into your spec, so you don't need to define them on every spec.

**NOTE:** Please note that the presenter is tested on isolation. It's not required but very recommended.

## Maintainers

- Felipe Elias Philipp - [coderwall.com/felipeelias](http://coderwall.com/felipeelias)
- Piotr Jakubowski - [coderwall.com/piotrj](http://coderwall.com/piotrj)

## Contributing

1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request