Halbuilder

license Gem Gem RSpec Standard

Hypertext Application Language (HAL) specific extensions for Jbuilder.

Installation

Install the gem and add to the application's Gemfile by executing:

$ bundle add halbuilder

If bundler is not being used to manage dependencies, install the gem by executing:

$ gem install halbuilder

Configuration

To customize Halbuilder, create an initializer in your Rails app at config/initializers/halbuilder.rb. Or if not using Rails, you can really configure this gem from anywhere:

# config/initializers/halbuilder.rb

Halbuilder.configure do |config|
  config.key_format = :camelize_lower
end

Available configurations are:

  • key_format (default :camelize_lower) - the key format used for Jbuilder formatting keys
    • This gem always formats nested keys
    • Allowed values are: nil :underscore :dasherize :camelize_lower :camelize_upper
  • link_namespace (default nil) - allow namespacing links in a way that breaks key_format
    • Can be any alphanumeric string
    • E.g. "hello" would cause json.hello_world "val" to generate {"hello:world":"val"}.
  • link_format (default :dasherize) - format namespaced links differently from key_format
    • Only applies if you set a non-nil link_namespace
    • Same allowed values as key_format
    • E.g. :dasherize would cause json.hello_world_there "val" to generate {"hello:world-there":"val"}

Usage

This library strives to be low-to-moderately opinionated about how you format your Jbuilder views. And there will always be many ways to build the same output. But - here are some general patterns for creating HAL JSON APIs.

The HAL _links object can be generated from anywhere within your *.json.jbuilder template, and given either a plain string href or yield a block object:

# just a string href
json.hal_link! "hello:one", "/api/v1/hello-one"

# or a block
json.hal_link! 'second' do
  json.href "/api/v1/second"
  json.title "my title"
end

# or manually add a link
json._links do
  json.third_link do
    json.href "/api/v1/thr33"
  end
end
{
  "_links": {
    "hello:one": {
      "href": "/api/v1/hello-one"
    },
    "second": {
      "href": "/api/v1/second",
      "title": "my title"
    },
    "thirdLink": {
      "href": "/api/v1/thr33"
    }
  }
}

Embeds

One convention for speeding up HAL APIs is to embed linked resources into the same documents. For this, we use the _embedded top level key, followed by the same rel as the link, and the object or array of objects (or null) you would get back had you followed the link.

json.hal_embed! "hello:one" do
  json.id 1234
  json.title "Some Resource"
end

json.hal_embed! "hello:two", 1..3 do |num|
  json.id num
  json.title "Thing #{num}"
end
{
  "_embedded": {
    "hello:one": {
      "id": 1234,
      "title": "Some Resource"
    },
    "hello:two": [
      {
        "id": 1,
        "title": "Thing 1"
      },
      {
        "id": 2,
        "title": "Thing 2"
      },
      {
        "id": 3,
        "title": "Thing 3"
      }
    ]
  }
}

Note that after the name of the rel, you can optionally pass a collection to iterate over. You can also pass a lambda, just-in-time returning either an object or collection to render:

json.hal_embed! "hello:one", -> { {id: 1234, title: "Some Resource"} } do |obj|
  json.id obj[:id]
  json.title obj[:title]
end

json.hal_embed! "hello:two", -> { 1..3 } do |num|
  json.id num
  json.title "Thing #{num}"
end

This works in conjunction with the ?zoom= query parameter. By passing zoom: true to a hal_embed! we indicate that by default, we want to render this collection. Or passing zoom: false indicates not to render it.

# GET /api/v1/resource

json.hal_embed! "hello:one", zoom: false do
  title "This will not render"
end

json.hal_embed! "hello:two", zoom: true do
  title "This WILL render"
end

Passing ?zoom=1 or ?zoom=0 will result in ALL hal_embed!s in the view being rendered or not rendered, regardless of their defaults.

Passing ?zoom=hello:one,hello:two will result in just those 2 rels being rendered, but any other hal_embed! with a zoom: will not be. This can be used in conjunction with lambdas/blocks to only load data needed for the requested zoom level:

# GET /api/v1/resource?zoom=hello:one,hello:two

json.hal_embed! "hello:one", zoom: false do
  # this code will run and render
  title some_expensive_db.get_title
end

json.hal_embed! "hello:two", -> { some_expensive_db.fetch_two }, zoom: true do |thing|
  # this code will also run and render
  title thing.title
end

json.hal_embed! "hello:three", -> { some_expensive_db.fetch_three }, zoom: true do |thing|
  # this code will NOT run the expensive db operation, OR render
  title thing.title
end

json.hal_embed! "hello:four", zoom: true do
  # this code will not run or render
  title some_expensive_db.get_title
end

Pagination

It's often useful to parse query params and render _links to different collection pages, using the gem Kaminari. After paging the collection in your controller, just call hal_paginate! on it:

# GET /api/v1/things?foo=bar&page=3

json.hal_paginate! @things
{
  "count": 10,
  "total": 999,
  "_links": {
    "first": {
      "href": "/api/v1/accounts?foo=bar"
    },
    "prev": {
      "href": "/api/v1/accounts?foo=bar&page=2"
    },
    "next": {
      "href": "/api/v1/accounts?foo=bar&page=4"
    },
    "last": {
      "href": "/api/v1/accounts?foo=bar&page=100"
    }
  }
}

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

Before commiting any code, run rake standardrb and/or rake standardrb --fix to make sure your changes are compliant with the Standard style guide.

When you're ready to push changes, open a pull request against the main branch of the repo.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/PRX/halbuilder. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Halbuilder project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.