Build ActiveModels from HAL documents with good error messages for validity issues.
Ruby
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
lib
spec
.gitignore
.travis.yml
Gemfile
LICENSE.txt
README.md
Rakefile
hal-interpretation.gemspec

README.md

Build Status Code Climate

HalInterpretation

Build ActiveModels from HAL documents with good error messages for validity issues.

Usage

HalInterpretation provides a DSL for declaring how to build one or more ActiveModel objects from a HAL document.

class UserHalInterpreter
  include HalInterpretation

  item_class User

  # Extract value of the name member of the JSON object and assign it to
  # the `name` attribute of the model.
  extract :name

  # Extract the value of the line1 member of the address member of the JSON
  # object and assign it to the `address_line` attribute of the model.
  extract :address_line, from: "address/line1"

  # Assign the `seq` attribute of the model a newly generated sequence number.
  extract :seq, with: ->(_hal_repr) { next_seq_num }

  # Extract the birthday member of the JSON object, convert it to a ruby date
  # and assign it to the `birthday` attribute of the model.
  extract :birthday, coercion: ->(date_str) { Date.iso8601(date_str) }

  # Extract the targets of the .../knows links, extract the ids from each and
  # assign those ids to the `friend_ids` attribute of the model.
  extract_links :friend_ids, coercion: ->(urls) { urls.map{|u| u.split("/").last} },
    rel: "http://xmlns.com/foaf/0.1/knows"

  # Extract the target of the up link and assign the full url to the up
  # attribute of the model. Reports a problem if more than one link of this
  # type is present.
  extract_link  :up

  # Extract the target of the rel link and assign a HAL representation to the person
  # attribute of the model. Reports a problem if more than one link of this
  # type is present.
  extract_related  :profile, rel: "http://xmlns.com/foaf/0.1/Person",
    coercion: ->(profile_repr) { CustomInterpretation.new(profile_repr) }

  # Extract the target of the rel link and assign a HAL representation
  # set to the cohorts attribute of the model. Reports a problem if
  # more than one link of this type is present.
  extract_relateds  :cohorts, rel: "http://xmlns.com/foaf/0.1/knows",
    coercion: ->(cohort_repr_set) {
      cohort_repr_set.map {|repr| CustomInterpretation.new(repr) }
    }

  def initialize
    @cur_seq_num = 0
  end

  protected

  def next_seq_num
    @cur_seq_num += 1
  end
end

This interpreter will work for documents that look like the following

{ "name": "Bob",
  "address": {
    "line1": "123 Main St",
    "city":  "Denver"
  },
  "birthday": "1980-08-31",
  "_links": {
    "http://xmlns.com/foaf/0.1/Person": { "href": "http://example.com/bob" },
    "http://xmlns.com/foaf/0.1/knows": [
      { "href": "http://example.com/alice" },
      { "href": "http://example.com/mallory" }
    ],
    "up": { "href": "http://example.com/vips" }
} }

or

{ "_embedded": {
    "item": [
      { "name": "Bob",
        "address": {
          "line1": "123 Main St",
          "city":  "Denver"
        },
        "birthday": "1980-08-31",
        "_links": {
          "http://xmlns.com/foaf/0.1/Person": { "href": "http://example.com/bob" },
          "http://xmlns.com/foaf/0.1/knows": [
            { "href": "http://example.com/alice" },
            { "href": "http://example.com/mallory" }
          ],
          "up": { "href": "http://example.com/vips" }
      } },

      { "name": "Alice",
        "address": {
          "line1": "123 Main St",
          "city":  "Denver"
        },
        "birthday": "1979-02-16",
        "_links": {
          "http://xmlns.com/foaf/0.1/Person": { "href": "http://example.com/alice },
          "http://xmlns.com/foaf/0.1/knows": [
            { "href": "http://example.com/bob" },
            { "href": "http://example.com/mallory" }
          ],
          "up": { "href": "http://example.com/vips" }
      } }
    ]
} }

Create

To interpret a HAL document simply create a new interpreter from the JSON document to interpret and then call its #items method.

class Users < ApplicationController
  def create
    @users = UserHalInterpreter.new_from_json(request.raw_post).items

    @users.each(&:save!)

  rescue HalInterpretation::InvalidRepresentationError => err
    render template: "shared/error", status: 422, locals: { problems: err.problems }
  end
end

The items method returns an Enumerable of valid item_class objects.

Update

To update an existing record

class Users < ApplicationController
  def update
    existing_user = User.find(params[:id])

    @user = UserHalInterpreter.new_from_json(request.raw_post).only_update(existing_user)
              .item

    @user.save!

  rescue HalInterpretation::InvalidRepresentationError => err
    render template: "shared/error", status: 422, locals: { problems: err.problems }
  end
end

This approach with produce an error if the JSON contains more than one representation.

Errors

If the JSON being interpreted is invalid or malformed HalInterpretation provides a list of the problems encountered through the #problems method. Each problem message includes a JSON pointer to the exact location in the original document that caused the problem. This is true even when interpreting collections for example if name of the third user in a collection is null the problem message would be

/_embedded/item/2/name cannot be blank

Validity is determined using the #valid? method of the models being built.

Installation

Add this line to your application's Gemfile:

gem 'hal-interpretation'

And then execute:

$ bundle

Or install it yourself as:

$ gem install hal-interpretation

Contributing

  1. Fork it ( http://github.com/pezra/hal-interpretation/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Make your improvement
  4. Update the version following semver rules
  5. Commit your changes (git commit -am 'Add some feature')
  6. Push to the branch (git push origin my-new-feature)
  7. Create new Pull Request