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
- Fork it ( http://github.com/pezra/hal-interpretation/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Make your improvement
- Update the version following semver rules
- Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request