Rafters lets you think about each page of your application as a collection of small pieces instead of monolithic, difficult to maintain views.
Add this line to your application's Gemfile:
gem 'rafters'And then execute:
$ bundleOr install it yourself as:
$ gem install raftersAfter you install the Rafters gem, you need to run the following generator:
$ rails generate rafters:installThis generator will create an app/rafters directory in your application, a config/initializers/rafters.rb initializer with some basic configuration, and add the require_components directive to your application.css and application.js files.
When you build out a page using Rafters you're effectively breaking it down into small, easy to digest chunks of code / markup that can optimally be configured and re-used on other pages as well. These bite-sized "views" are called Components, and you can think of them as partials on steroids (with their own "controllers" and templates).
To begin creating a new component, run the following generator:
$ rails generate rafters:component {name}This generator will create the following files:
app/rafters/{name}
app/rafters/{name}/{name}_component.rb
app/rafters/{name}/assets/stylesheets/{name}_component.scss
app/rafters/{name}/assets/javascripts/{name}_component.js.coffee
app/rafters/{name}/views/{name}_component.html.erb
The two most important files generated above are app/rafters/{name}/{name}_component.rb and app/rafters/{name}/views/{name}_component.html.erb, which are (respectively) our component controller and view.
You can render components anywhere - in your view, in your controller, in another component, etc. - but the most common place will (obviously) be in your views. To render a component, call render_component :component_name, as: "unique-identifier" in one of your app views. For example:
...
<div class="main">
<%= render_component :heading, as: "page-heading" %>
</div>
...Each component exposes attributes to it's view as locals. The attributes are simply a collection of methods that you explicitly declare as attributes in your component, using the Rafters::Component.attribute or Rafters::Component.attributes methods.
For instance, let's say we have a HeadingComponent that exposes a title attribute:
class HeadingComponent
include Rafters::Component
attribute :title
private
def title
"Lorem Ipsum"
end
endSince we won't be accessing the HeadingComponent#title method directly from within our view, it's recommended to make it a private method. The interface that our component exposes is taken care of behind the scenes.
You can access the method in your component view using the name of the attribute:
<div class="heading">
<h1><%= title %></h1>
</div>There will often be times when you need to access data in your component that is only available as an instance variable or method in your controller. Rafters provides a convenience method that lets you get to that data in a uniform way - Rafters::Component#controller:
class PostController
...
def show
@post = current_user.posts.find(params[:id])
end
private
def current_user
@current_user ||= User.authenticate!(...)
end
helper_method :current_user
endclass RelatedPostsComponent
...
def related_posts
@related_posts ||= controller(:post).related_posts.where(author_id: controller(:current_user))
end
endYou can also access the controller's params using this method:
controller(:params)[:id]In order to build components in a way that allows for re-use, you'll want to use settings that allow individual instances of the component to be configured. These settings will likely be used throughout your component view and controller for any number of purposes, like values in query conditions, section titles, turning on or off specific features of a component, etc.
Setting values are specified when rendering a component:
...
<div class="main">
<%= render_component :posts, as: "published-posts", settings: { published: true } %>
</div>
...And can be accessed via the settings object in your component view:
<div class="posts">
<% if settings.published? %>
...
<% else %>
...
<% end %>
</div>Or your component controller:
class PostsComponent
...
def posts
Post.where(published: settings.published)
end
endDefault values can be provided for settings using Rafters::Component.defaults and Rafters::Component.default:
defaults type: "comment", filter: "none"
default :published, falseThere are often times when a single, general component can be powered by multiple complex data sources. In order to do this Rafters includes to option to create "source" classes that belong to individual components. Each source should expose an interface that can be consumed by the component's template, and options that are set on the component level are accessible at the source level as well.
To create a source, run the following generator:
$ rails g rafters:source {component_name} {source_name}This generator will create a file named {component_name}_{source_name}_source.rb in the specified component's sources directory.
A source class is a simple Ruby class that includes the Rafters::Source module. You can do pretty much anything you want with source classes (including subclassing), as long as they all expose the same public interface methods. When a component is configured to use a specific source, the #source method in that component will return an instance of the specified source class.
Here's a basic example - a list-view component with one source:
The component itself:
class ListViewComponent
include Rafters::Component
attribute :items
private
def items
source.items
end
endA source for the component:
class ListViewCommentSource
include Rafters::Source
def items
@items ||= Comment.all
end
endThe component's view template:
<ul>
<% items.each do |item| %>
<li><%= item.body %></li>
<% end %>
</ul>To render the above component with the ListViewCommentSource:
<div class="main">
<%= render_component :list_view, as: "comments-list", source: "ListViewCommentSource"
</div>
If you want to add another source option for this component, simply create a new source class with the same interface:
class ListViewPostSource
include Rafters::Source
def items
@items ||= Post.where({
category_id: settings.category_id
}).all
end
endAnd render the component with the new source (and any settings that it may require):
<div class="main">
<%= render_component :list_view, as: "posts-list", source: "ListViewPostSource", {
category_id: @category.id
} %>
</div>The Rafters::Component#controller and Rafters::Component#settings methods are both available within your source classes, and will return the same information that they would in the related component.
- Fork it
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create new Pull Request



