Skip to content

andrewcroome/bird-on-it

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bird On It: Simple decorators for your Rails models

Model need decorating in your Rails view? Put a bird on it!

What is Bird On It?

Bird On It is a simple way to decorate models in your Rails application.

Decorators allow you to move view- and presentation-related logic out of your active record models and view helpers and into decorator classes. Doing this cleans up your view code and reduces the number of responsibilities your active record objects have.

Here's how it works. Let's say you have a ToteBag model, with colour, size and straps attributes. In your view, you want to describe whether or not the bag has straps. You also want to add some css classes, depending on what properties the bag has. You put the first of these methods on the model, and the second in a helper, like so:

# app/models/tote_bag.rb
class ToteBag < ActiveRecord::Base
  def straps_description
    if straps?
      'This bag has straps'
    else
      'This bag does not have straps'
    end
  end
end
# app/helpers/tote_bags_helper.rb
module ToteBagHelper
  def tote_bag_css_classes(tote_bag)
    tote_bag_css_classes = %w{tote-bag}
    tote_bag_css_classes << 'straps' if tote_bag.straps?
    tote_bag_css_classes.join(' ')
  end
end

In your view, you would use both the helper and model:

<div class="<%= tote_bag_css_classes(@tote_bag) %>">
  <h2>Straps:</h2>
  <p><%= @tote_bag.straps_description %></p>
</div>

Instead of the above, however, using a tote bag decorator allows both these methods to exist in the one place. The decorator sends any method that it doesn't itself respond to the decorated object. Using Bird On It:

# app/models/tote_bag.rb
class ToteBag < ActiveRecord::Base
  include BirdOnIt
end
# app/decorators/tote_bag_decorator.rb
class ToteBagDecorator
  include BirdOnIt::Decorator

  def straps_description
    if object.straps?
      'This bag has straps'
    else
      'This bag does not have straps'
    end
  end

  def css_classes
    css_classes = %w{tote-bag}
    css_classes << 'straps' if object.straps?
    css_classes.join(' ')
  end
end

In your view:

<div class="<%= @tote_bag.css_classes %>">
  <h2>Straps:</h2>
  <p><%= @tote_bag.straps_description %></p>
</div>

Now your view-related code can reside in the decorator, eliminating the need for helper methods and giving your tote bag class one less responsibility.

Installation

Add Bird On It to your Gemfile

  gem 'bird_on_it'

Run bundle install.

Usage

Model

To decorate a model, first include Bird On It:

# app/models/post.rb
class Post < ActiveRecord::Base
  include BirdOnIt
end

Then create a decorator either in app/views/decorators or app/models with the name matching your model and include BirdOnIt::Decorator.

# app/decorators/post_decorator.rb
class PostDecorator < ActiveRecord::Base
  include BirdOnIt::Decorator

  def display_title
    object.title.humanize
  end
end

In the decorator, your decorated object is available via the object variable. If you prefer, you can also access it simply by calling methods to which your decorator does not respond:

def display_title
  title.humanize
end

Controller

Bird On It adds a decorate method you can call on your objects in your controller before they are passed to the view.

# app/controllers/posts_controller.rb
def show
  @post = Post.find(params[:id]).decorate
end

There is also a decorate_collection class method for decorating collections.

# app/controllers/posts_controller.rb
def index
  @posts = Post.decorate_collection(Post.all)
end

View

Use your decorated objects in the view as usual.

<div class="post">
  <h1><%= @post.display_title %></h2>
  <p><%= @post.body %></p>
</div>

Some Rails helpers, such as the edit_path helper, do not work well with decorated objects. If you encounter one of these, you can work around this by passing the helper the decorated object, instead of the decorator. For example:

<%= link_to 'Edit', edit_post_path(@post.object) %>

Other features, such as linking to the show page, or using the object with form_for, should continue to work as usual.

<%= link_to 'Show', @post %>
<%= form_for(@post) do |f| %>
  <%= f.text_field :material %>
  <%= f.submit %>
<% end %>

This project uses MIT-LICENSE.