Project

curlybars

0.01
A long-lived project that still receives updates
A view layer for your Rails apps that separates structure and logic, using Handlebars templates. Strongly inspired by Curly Template gem by Daniel Schierbeck.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

Runtime

>= 0
>= 0
 Project Readme

Build Status

Curlybars

Curlybars is a Ruby implementation of a subset of Handlebars, where getting the context for rendering the template is based on the presenter approach taken in Curly.

Table of Contents

  1. Overview
  2. Getting started
  3. Configuration
  4. Further documentation
  5. Contributing
  6. Maintainers
  7. Releasing
  8. Copyright and License

Overview

Curlybars is a templating engine for Ruby, and it integrates with Rails out of the box. In order to use it with Rails, at least a root presenter and a template must be provided for each view that is meant to be rendered via Curlybars.

A template is, in fact, a Handlebars template and must use the .hbs instead of .erb extension, such as app/views/invoice/details.html.hbs.

A root presenter is a class providing the root context used to evaluate a given template. In other words, if the template contains the string {{amount}}, then the root presenter must provide an instance method named amount.

Whenever a deeper reference like path is encountered in a template, like {{recipient.name}}, the root presenter is expected to have a method named recipient, returning an instance of a Poor Old Ruby Object presenter (a non-root presenter) with a method called name.

More on what has been introduced here will be explained in details later sections.

Getting started

To provide your Rails app with Curlybars, simply add the following line in your Gemfile, possibly narrowing down the version you want to depend on:

gem 'curlybars'

And then simply run bundle install. At this point, we can start to create a trivial example, that shows how to get started with rendering a page.

For starters, let's create a couple of classes, Invoice and Recipient, that hold part of the information that would be in an invoice.

# app/models/invoice.rb

class Invoice
  def initialize(amount:, recipient:)
    @amount = amount
    @recipient = recipient
  end

  def amount
    @amount
  end

  def recipient
    @recipient
  end
end
# app/models/recipient.rb

class Recipient
  def initialize(name:)
    @name = name
  end

  def name
    @name
  end
end

Then, let's add a controller to implement the show action for invoices, as follows.

# app/controllers/invoices_controller.rb

class InvoicesController < ApplicationController
  def show
    recipient = Recipient.new(name: "John Venturini")
    @invoice = Invoice.new(amount: 100, recipient: recipient)
  end
end

If we provide a Handlebars template at app/views/invoices/show.html.hbs, then by Rails convention it will be used. Let's get the following snippet to be the content of the template:

{{! app/views/invoices/show.html.hbs }}

<h1>Amount #{{amount}} (issued on {{date}})</h1>
<h2>To: {{recipient.name}}</h2>

The template in this example is only showing how it's possible to quickly put a simple view together, but there's much more to say about the language specification that is thoroughly covered in docs/templates.md.

As you can see, there are regular Handlebars paths within curlies ({{ and }}). In a situation where we'd use Handlebars we would have to provide a context as well, so that for instance recipient.name can be resolved to an actual value to replace {{recipient.name}}. Curlybars provides a context in a similar way Curly does: namely, using presenters like the following one.

# app/presenters/invoices/show_presenter.rb

class Invoices::ShowPresenter < Curlybars::Presenter
  presents :invoice

  allow_methods :amount, :date,
    recipient: RecipientPresenter

  def amount
    @invoice.amount
  end

  def date
    @invoice.created_at.to_formatted_s(:short)
  end

  def recipient
    RecipientPresenter.new(@invoice.recipient)
  end
end

Presenters of this kind, extending Curlybars::Presenter, are referred to as root presenters. Note that root presenters will be looked up by Rails in a similar way views are. All the presenters have to reside in the app/presenters folder, within a subfolder named after the model they are used for (in this case, invoices) and have a name that follows the <action>_presenter.rb pattern. The presenter in the example above, for instance, would be looked up at app/presenters/invoices/show_presenter.rb.

When extending Curlybars::Presenter, we get access to a convenience method .allow_methods to explicitly whitelist and describe whan can be done with this presenter. In the example above, we are declaring that from the template we can access the amount and date fields using curlies (namely, {{amount}} and {{date}}) and that an extra presenter can be accessed via recipient and traversed using the path syntax.

RecipientPresenter would be a PORO presenter, and as such we only need to extend Curlybars::MethodWhitelist so that allow_methods can be used.

You can find more about root presenters, PORO presenters and Curlybars::MethodWhitelist on docs/presenters.md.

# app/presenters/recipient_presenter.rb

class RecipientPresenter
  extend Curlybars::MethodWhitelist

  allow_methods :name

  def initialize(recipient)
    @recipient = recipient
  end

  def name
    @recipient.name
  end
end

On a side note, describing the accessible data via .allow_methods also allows Curlybars to perform templates validation: in other words, Curlybars is able to tell wether a template is going to be rendered successfully or not, by simply looking at the template itself and at what has been declared accessible.

At this point, running the Rails app and going to the page configured to show the page of this example, would render the Handlebars template as expected, according to what the presenter will provide.

Besides data, presenters can also provide helper methods. A helper is a method that accepts arguments and/or options, and returns a value derivated from them. A detailed explanation about how helpers can be useful and which forms they come with, refer to docs/helpers.md.

Errors can be raised when using Curlybars incorrectly, and you can refer to docs/errors.md for further details.

Configuration

Curlybars offers configuration options aimed to fine-tune runtime constraints, useful for when you need to make sure a page rendering is aborted when the size reaches a certain limit, or when it takes too long.

Getting some configuration in Rails is as simple as adding an initializer (e.g., config/initializers/curlybars.rb) having a similar content like the following.

Curlybars.configure do |config|
  config.output_limit = 2.megabytes
  config.rendering_timeout = 5.seconds
end

More on configuration options can be found in docs/configuration.md.

Further documentation

  1. Templates
  2. Presenters
  3. Helpers
  4. Configuration
  5. Errors

Contributing

Contributing to Curlybars is fairly easy, as long as the following steps are followed:

  1. Fork the project
  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 a new Pull Request
  6. Mention one or more of the maintainers to get the Pull Request approved and merged

Maintainers

This project is maintained by @zendesk/vikings

Releasing

  • git checkout master && git pull
  • Update changelog
  • Bump version in lib/curlybars/version.rb
  • Commit changes
  • rake release

More information

Copyright and License

Copyright (c) 2017 Zendesk Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.

You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.