hal-interpretation 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +84 -0
- data/Rakefile +6 -0
- data/hal-interpretation.gemspec +33 -0
- data/lib/hal-interpretation.rb +1 -0
- data/lib/hal_interpretation/dsl.rb +22 -0
- data/lib/hal_interpretation/errors.rb +19 -0
- data/lib/hal_interpretation/extractor.rb +34 -0
- data/lib/hal_interpretation/item_interpreter.rb +74 -0
- data/lib/hal_interpretation/version.rb +3 -0
- data/lib/hal_interpretation.rb +105 -0
- data/spec/hal_interpretation_spec.rb +150 -0
- data/spec/spec_helper.rb +1 -0
- metadata +188 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 307909b10b6823175a275e546f52f1b2451db046
|
4
|
+
data.tar.gz: bb0969dc221c78829b4fec9f0f1029395a3875f1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c3cb5970235c8d729f3d7f4eaba0637cfce427fbefeb51f647cfc3f236df8c34e53046c17f3cbc778dacd13fac36a10f36d4783086ebd3282469f17631fb6bf9
|
7
|
+
data.tar.gz: 345c2f5de8ac2cad5f1fce84fd62848db9f8526bb762df1474fa8a957ad178d96ad4667c723ef3dbdcbe490081fe6bf55f1dfacec51e00f484f3cb7b93d8b9ad
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Peter Williams
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
[](https://travis-ci.org/pezra/hal-interpretation)
|
2
|
+
[](https://codeclimate.com/github/pezra/hal-interpretation)
|
3
|
+
|
4
|
+
# HalInterpretation
|
5
|
+
|
6
|
+
Build ActiveModels from HAL documents with good error messages
|
7
|
+
for validity issues.
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
`HalInterpretation` provides a DSL for declaring how to build one or
|
12
|
+
more `ActiveModel` objects from a HAL document.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class UserHalInterpreter
|
16
|
+
include HalInterpretation
|
17
|
+
|
18
|
+
item_class User
|
19
|
+
|
20
|
+
extract :name
|
21
|
+
extract :address_line, from: "address/line1"
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
To interpret a HAL document simply create a new interpreter from the
|
26
|
+
JSON document to interpret and then call its `#items` method.
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
class Users < ApplicationController
|
30
|
+
def create
|
31
|
+
@users = UserHalInterpreter.new_from_json(request.raw_post).items
|
32
|
+
|
33
|
+
rescue HalInterpretation::InvalidRepresentationError => err
|
34
|
+
render template: "shared/error", status: 422, locals: { problems: err.problems }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
The `items` method returns an `Enumerable` of valid `item_class` objects.
|
40
|
+
|
41
|
+
### Errors
|
42
|
+
|
43
|
+
If the JSON being interpreted is invalid or malformed
|
44
|
+
`HalInterpretation` provides a list of the problems encountered
|
45
|
+
through the `#problems` method. Each problem message includes a
|
46
|
+
[JSON pointer][] to the exact location in the original document that
|
47
|
+
caused the problem. This is true even when interpreting
|
48
|
+
[collections][] for example if name of the third user in a collection
|
49
|
+
is null the problem message would be
|
50
|
+
|
51
|
+
/_embedded/item/2/name cannot be blank
|
52
|
+
|
53
|
+
Validity is determined using the `#valid?` method of the models being
|
54
|
+
built.
|
55
|
+
|
56
|
+
|
57
|
+
## Installation
|
58
|
+
|
59
|
+
Add this line to your application's Gemfile:
|
60
|
+
|
61
|
+
gem 'hal-interpretation'
|
62
|
+
|
63
|
+
And then execute:
|
64
|
+
|
65
|
+
$ bundle
|
66
|
+
|
67
|
+
Or install it yourself as:
|
68
|
+
|
69
|
+
$ gem install hal-interpretation
|
70
|
+
|
71
|
+
## Contributing
|
72
|
+
|
73
|
+
1. Fork it ( http://github.com/pezra/hal-interpretation/fork )
|
74
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
75
|
+
3. Make your improvement
|
76
|
+
4. Update the version following [semver][] rules
|
77
|
+
5. Commit your changes (`git commit -am 'Add some feature'`)
|
78
|
+
6. Push to the branch (`git push origin my-new-feature`)
|
79
|
+
7. Create new Pull Request
|
80
|
+
|
81
|
+
|
82
|
+
[semver]: http://semver.org/
|
83
|
+
[json pointer]: http://tools.ietf.org/html/rfc6901
|
84
|
+
[collections]: https://tools.ietf.org/html/rfc6573
|
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hal_interpretation/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hal-interpretation"
|
8
|
+
spec.version = HalInterpretation::VERSION
|
9
|
+
spec.authors = ["Peter Williams"]
|
10
|
+
spec.email = ["pezra@barelyenough.org"]
|
11
|
+
spec.summary = %q{Build models from HAL documents.}
|
12
|
+
spec.description = %q{Declarative creation of ActiveModels from HAL documents.}
|
13
|
+
spec.homepage = "https://github.com/pezra/hal-interpretation"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.required_ruby_version = ">=1.9.3"
|
22
|
+
|
23
|
+
spec.add_dependency "hal-client", "~>2.2"
|
24
|
+
spec.add_dependency "hana", "~>1.2"
|
25
|
+
spec.add_dependency "multi_json", "~>1.9"
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
28
|
+
spec.add_development_dependency "rake", "~>10.1"
|
29
|
+
spec.add_development_dependency "rspec", "~>3.0.0.beta"
|
30
|
+
spec.add_development_dependency "rspec-collection_matchers", "~>0.0.3"
|
31
|
+
spec.add_development_dependency "activemodel", "~>4.0"
|
32
|
+
spec.add_development_dependency "activesupport", "~>4.0"
|
33
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "hal_interpretation"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module HalInterpretation
|
2
|
+
module Dsl
|
3
|
+
# Declare the class of models this interpreter builds.
|
4
|
+
def item_class(klass)
|
5
|
+
define_method(:item_class) do
|
6
|
+
klass
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Declare that an attribute should be extract from the HAL
|
11
|
+
# document.
|
12
|
+
#
|
13
|
+
# attr_name - name of attribute on model to extract
|
14
|
+
# opts - hash of named arguments to method
|
15
|
+
# :from - JSON path from which to get the value for
|
16
|
+
# attribute. Default: "/#{attr_name}"
|
17
|
+
def extract(attr_name, opts={})
|
18
|
+
from = opts.fetch(:from) { "/#{attr_name}" }
|
19
|
+
extractors << Extractor.new(attr: attr_name, location: from)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module HalInterpretation
|
2
|
+
Error = Class.new(StandardError)
|
3
|
+
|
4
|
+
class InvalidRepresentationError < Error
|
5
|
+
# Initializes a new instance of this error
|
6
|
+
#
|
7
|
+
# problems - list of problems detected with the representation.
|
8
|
+
def initialize(problems)
|
9
|
+
@problems = problems = Array(problems)
|
10
|
+
|
11
|
+
msg = problems.first
|
12
|
+
msg += " and #{problems.count - 1} more problems" if problems.count > 1
|
13
|
+
|
14
|
+
super msg
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :problems
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'hana'
|
2
|
+
|
3
|
+
module HalInterpretation
|
4
|
+
class Extractor
|
5
|
+
# opts - named args
|
6
|
+
# :attr - name of attribute this object will extact.
|
7
|
+
# :location - JSON path from which to get the value.
|
8
|
+
def initialize(opts) #attr:, location: "/#{attr}")
|
9
|
+
@attr = opts.fetch(:attr) { fail ArgumentError, "attr is required" }
|
10
|
+
@location = opts.fetch(:location) { "/#{attr}" }
|
11
|
+
@pointer = Hana::Pointer.new(location)
|
12
|
+
end
|
13
|
+
|
14
|
+
# opts - named args
|
15
|
+
# :from - The HalRepresentation from which to extract attribute.
|
16
|
+
# :to - The model that we are extracting
|
17
|
+
#
|
18
|
+
# Returns any problems encountered.
|
19
|
+
def extract(opts)
|
20
|
+
from = opts.fetch(:from) { fail ArgumentError, "from is required" }
|
21
|
+
to = opts.fetch(:to) { fail ArgumentError, "to is required" }
|
22
|
+
to.send "#{attr}=", pointer.eval(from)
|
23
|
+
|
24
|
+
[]
|
25
|
+
rescue => err
|
26
|
+
[err.message]
|
27
|
+
end
|
28
|
+
|
29
|
+
attr_reader :attr, :location
|
30
|
+
|
31
|
+
protected
|
32
|
+
attr_reader :pointer
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module HalInterpretation
|
2
|
+
class ItemInterpreter
|
3
|
+
# opts - named args
|
4
|
+
# :location -
|
5
|
+
# :interpreter -
|
6
|
+
def initialize(a_representation, opts)
|
7
|
+
@repr = a_representation
|
8
|
+
@location = opts.fetch(:location) { fail ArgumentError, "location is required" }
|
9
|
+
@problems = []
|
10
|
+
@interpreter = opts.fetch(:interpreter) { fail ArgumentError, "interpreter is required" }
|
11
|
+
end
|
12
|
+
|
13
|
+
def items
|
14
|
+
interpret unless done?
|
15
|
+
|
16
|
+
(raise InvalidRepresentationError.new problems) if problems.any?
|
17
|
+
|
18
|
+
@items
|
19
|
+
end
|
20
|
+
|
21
|
+
def problems
|
22
|
+
interpret unless done?
|
23
|
+
|
24
|
+
@problems
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
extend Forwardable
|
30
|
+
|
31
|
+
def_delegators :interpreter, :extractors, :extractor_for, :item_class
|
32
|
+
|
33
|
+
attr_reader :repr, :location, :interpreter
|
34
|
+
|
35
|
+
def done?
|
36
|
+
!@items.nil? || @problems.any?
|
37
|
+
end
|
38
|
+
|
39
|
+
def interpret
|
40
|
+
new_item = item_class.new do |it|
|
41
|
+
e = extractors
|
42
|
+
e.each do |an_extractor|
|
43
|
+
@problems += an_extractor.extract(from: repr, to: it)
|
44
|
+
.map {|msg| "#{json_path_for an_extractor.attr} #{msg}" }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
apply_validations(new_item)
|
49
|
+
|
50
|
+
return if @problems.any?
|
51
|
+
|
52
|
+
@items = [new_item]
|
53
|
+
end
|
54
|
+
|
55
|
+
def apply_validations(an_item)
|
56
|
+
an_item.valid?
|
57
|
+
an_item.errors.each do |attr, msg|
|
58
|
+
@problems << "#{json_path_for attr} #{msg}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def json_path_for(attr)
|
63
|
+
json_pointer_join(location, extractor_for(attr).location)
|
64
|
+
end
|
65
|
+
|
66
|
+
def json_pointer_join(head, tail)
|
67
|
+
head = head[0..-2] if head.end_with?("/")
|
68
|
+
tail = tail[1..-1] if tail.start_with?("/")
|
69
|
+
|
70
|
+
head + "/" + tail
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "multi_json"
|
3
|
+
require "hal-client"
|
4
|
+
|
5
|
+
# Declarative interpretation of HAL documents into ActiveModel style
|
6
|
+
# objects.
|
7
|
+
module HalInterpretation
|
8
|
+
|
9
|
+
# Returns array of models created from the HAL representation we are
|
10
|
+
# interpreting.
|
11
|
+
#
|
12
|
+
# Raises InvalidRepresentationError if any of the models are invalid
|
13
|
+
# or the representation is not a HAL document.
|
14
|
+
def items
|
15
|
+
(fail InvalidRepresentationError.new(problems)) if problems.any?
|
16
|
+
|
17
|
+
@observations ||= interpreters.flat_map(&:items)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns array of problems messages, or empty array if there are
|
21
|
+
# none. This will do a complete interpretation of the representation
|
22
|
+
# if it has not already been done.
|
23
|
+
def problems
|
24
|
+
@problems ||= interpreters.flat_map(&:problems)
|
25
|
+
end
|
26
|
+
|
27
|
+
extend Forwardable
|
28
|
+
def_delegators "self.class", :extractors, :extractor_for
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
# opts
|
33
|
+
# :location - The json path of `a_representation` in the
|
34
|
+
# complete document
|
35
|
+
def initialize(a_representation, opts)
|
36
|
+
@repr = a_representation
|
37
|
+
@location = opts.fetch(:location) { raise ArgumentError, "location is required" }
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :repr, :location
|
41
|
+
|
42
|
+
def interpreters
|
43
|
+
@interpreters ||=
|
44
|
+
begin
|
45
|
+
if repr.has_related? 'item'
|
46
|
+
repr
|
47
|
+
.related('item')
|
48
|
+
.each_with_index
|
49
|
+
.map{ |item_repr, idx|
|
50
|
+
ItemInterpreter.new(item_repr,
|
51
|
+
location: location + "_embedded/item/#{idx}/",
|
52
|
+
interpreter: self) }
|
53
|
+
else
|
54
|
+
[ItemInterpreter.new(repr, location: location, interpreter: self)]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Back stop method to be overridden by individual interpreters.
|
60
|
+
def item_class
|
61
|
+
fail NotImplementedError, "interpreter classes must call `item_class <model class>` in the class defintion"
|
62
|
+
end
|
63
|
+
|
64
|
+
module ClassMethods
|
65
|
+
# Returns new interpreter for the provided JSON document.
|
66
|
+
#
|
67
|
+
# Raises HalInterpretation::InvalidRepresentationError if the
|
68
|
+
# provided JSON document is not parseable
|
69
|
+
def new_from_json(json)
|
70
|
+
self.new HalClient::Representation.new(parsed_json: MultiJson.load(json)),
|
71
|
+
location: "/"
|
72
|
+
|
73
|
+
rescue MultiJson::ParseError => err
|
74
|
+
fail InvalidRepresentationError, "Parse error: " + err.message
|
75
|
+
end
|
76
|
+
|
77
|
+
# internal stuff
|
78
|
+
|
79
|
+
# Returns collection of attribute extractors.
|
80
|
+
def extractors
|
81
|
+
@extractors ||= []
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the attribute extractor for the specified attribute.
|
85
|
+
def extractor_for(attr_name)
|
86
|
+
extractors.find {|it| it.attr == attr_name }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def self.included(klass)
|
92
|
+
klass.extend ClassMethods
|
93
|
+
klass.extend Dsl
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
autoload :Dsl, "hal_interpretation/dsl"
|
98
|
+
autoload :ItemInterpreter, "hal_interpretation/item_interpreter"
|
99
|
+
autoload :Extractor, "hal_interpretation/extractor"
|
100
|
+
autoload :Error, "hal_interpretation/errors"
|
101
|
+
autoload :InvalidRepresentationError, "hal_interpretation/errors"
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
require "hal_interpretation/version"
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require "active_support"
|
3
|
+
require "active_model"
|
4
|
+
require "rspec/collection_matchers"
|
5
|
+
|
6
|
+
describe HalInterpretation do
|
7
|
+
subject(:interpreter_class) {
|
8
|
+
test_item_class = self.test_item_class
|
9
|
+
Class.new do
|
10
|
+
include HalInterpretation
|
11
|
+
item_class test_item_class
|
12
|
+
extract :name
|
13
|
+
extract :latitude, from: "/geo/latitude"
|
14
|
+
end }
|
15
|
+
|
16
|
+
context "valid single item" do
|
17
|
+
let(:json_doc) { <<-JSON }
|
18
|
+
{ "name": "foo"
|
19
|
+
,"geo": {
|
20
|
+
"latitude": 39.1
|
21
|
+
}
|
22
|
+
}
|
23
|
+
JSON
|
24
|
+
|
25
|
+
specify { expect(interpreter.items).to have(1).item }
|
26
|
+
specify { expect(interpreter.items.first.name).to eq "foo" }
|
27
|
+
specify { expect(interpreter.items.first.latitude).to eq 39.1 }
|
28
|
+
specify { expect(interpreter.problems).to be_empty }
|
29
|
+
end
|
30
|
+
|
31
|
+
context "valid collection" do
|
32
|
+
let(:json_doc) { <<-JSON }
|
33
|
+
{ "_embedded": {
|
34
|
+
"item": [{ "name": "foo"
|
35
|
+
,"geo": {
|
36
|
+
"latitude": 39.1
|
37
|
+
}
|
38
|
+
}
|
39
|
+
,{ "name": "bar"
|
40
|
+
,"geo": {
|
41
|
+
"latitude": 39.2
|
42
|
+
}
|
43
|
+
}]
|
44
|
+
}
|
45
|
+
}
|
46
|
+
JSON
|
47
|
+
|
48
|
+
specify { expect(interpreter.problems).to be_empty }
|
49
|
+
specify { expect(interpreter.items).to have(2).items }
|
50
|
+
specify { expect(interpreter.items).to include item_named "foo" }
|
51
|
+
specify { expect(interpreter.items).to include item_named "bar" }
|
52
|
+
|
53
|
+
matcher :item_named do |expected_name|
|
54
|
+
match do |obj|
|
55
|
+
obj.name == expected_name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "invalid attributes" do
|
61
|
+
let(:json_doc) { <<-JSON }
|
62
|
+
{ "geo": {
|
63
|
+
"latitude": "hello"
|
64
|
+
}
|
65
|
+
}
|
66
|
+
JSON
|
67
|
+
|
68
|
+
specify { expect{interpreter.items}
|
69
|
+
.to raise_exception HalInterpretation::InvalidRepresentationError }
|
70
|
+
context "raised error" do
|
71
|
+
subject(:error) { interpreter.items rescue $! }
|
72
|
+
|
73
|
+
specify { expect(error.problems)
|
74
|
+
.to include matching matching(%r(/geo/latitude\b)).and(match(/\binvalid value\b/i)) }
|
75
|
+
specify { expect(error.problems)
|
76
|
+
.to include matching(%r(/name\b)).and(match(/\bblank\b/i)) }
|
77
|
+
end
|
78
|
+
specify { expect(interpreter.problems)
|
79
|
+
.to include matching(%r(/name\b)).and(match(/\bblank\b/i)) }
|
80
|
+
specify { expect(interpreter.problems)
|
81
|
+
.to include matching(%r(/geo/latitude\b)).and(match(/\binvalid value\b/i)) }
|
82
|
+
end
|
83
|
+
|
84
|
+
context "collection w/ invalid attributes" do
|
85
|
+
let(:json_doc) { <<-JSON }
|
86
|
+
{ "_embedded": {
|
87
|
+
"item": [{ "geo": {
|
88
|
+
"latitude": "hello"
|
89
|
+
}
|
90
|
+
}]
|
91
|
+
}
|
92
|
+
}
|
93
|
+
JSON
|
94
|
+
|
95
|
+
specify { expect{interpreter.items}
|
96
|
+
.to raise_exception HalInterpretation::InvalidRepresentationError }
|
97
|
+
specify { expect(interpreter.problems)
|
98
|
+
.to include matching(%r(/_embedded/item/0/name\b)).and(match(/\bblank\b/i)) }
|
99
|
+
specify { expect(interpreter.problems)
|
100
|
+
.to include matching(%r(/_embedded/item/0/geo/latitude\b))
|
101
|
+
.and(match(/\binvalid value\b/i)) }
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
context "missing compound member" do
|
106
|
+
let(:json_doc) { <<-JSON }
|
107
|
+
{ "name": "nowhere" }
|
108
|
+
JSON
|
109
|
+
|
110
|
+
specify { expect{interpreter.items}
|
111
|
+
.to raise_exception HalInterpretation::InvalidRepresentationError}
|
112
|
+
specify { expect(interpreter.problems)
|
113
|
+
.to include matching(%r(/geo/latitude\b)).and(match(/\bblank\b/i)) }
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
context "non-json doc" do
|
118
|
+
let(:non_json_doc) { "what's json" }
|
119
|
+
|
120
|
+
specify { expect{interpreter_class.new_from_json(non_json_doc)}
|
121
|
+
.to raise_exception HalInterpretation::InvalidRepresentationError, /\bparse\b/i }
|
122
|
+
end
|
123
|
+
|
124
|
+
let(:interpreter) { interpreter_class.new_from_json(json_doc) }
|
125
|
+
|
126
|
+
let(:test_item_class) { Class.new do
|
127
|
+
include ActiveModel::Validations
|
128
|
+
|
129
|
+
attr_accessor :name, :latitude
|
130
|
+
|
131
|
+
def initialize
|
132
|
+
yield self
|
133
|
+
end
|
134
|
+
|
135
|
+
def latitude=(lat)
|
136
|
+
@latitude = if !lat.nil?
|
137
|
+
Float(lat)
|
138
|
+
else
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.model_name
|
144
|
+
ActiveModel::Name.new(self, nil, "temp")
|
145
|
+
end
|
146
|
+
|
147
|
+
validates :name, presence: true
|
148
|
+
validates :latitude, presence: true
|
149
|
+
end }
|
150
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "hal-interpretation"
|
metadata
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hal-interpretation
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Williams
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hal-client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hana
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: multi_json
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.9'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.9'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.5'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.5'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.1'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.1'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 3.0.0.beta
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 3.0.0.beta
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec-collection_matchers
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.0.3
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.0.3
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: activemodel
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ~>
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '4.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ~>
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '4.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: activesupport
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ~>
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '4.0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ~>
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '4.0'
|
139
|
+
description: Declarative creation of ActiveModels from HAL documents.
|
140
|
+
email:
|
141
|
+
- pezra@barelyenough.org
|
142
|
+
executables: []
|
143
|
+
extensions: []
|
144
|
+
extra_rdoc_files: []
|
145
|
+
files:
|
146
|
+
- .gitignore
|
147
|
+
- .travis.yml
|
148
|
+
- Gemfile
|
149
|
+
- LICENSE.txt
|
150
|
+
- README.md
|
151
|
+
- Rakefile
|
152
|
+
- hal-interpretation.gemspec
|
153
|
+
- lib/hal-interpretation.rb
|
154
|
+
- lib/hal_interpretation.rb
|
155
|
+
- lib/hal_interpretation/dsl.rb
|
156
|
+
- lib/hal_interpretation/errors.rb
|
157
|
+
- lib/hal_interpretation/extractor.rb
|
158
|
+
- lib/hal_interpretation/item_interpreter.rb
|
159
|
+
- lib/hal_interpretation/version.rb
|
160
|
+
- spec/hal_interpretation_spec.rb
|
161
|
+
- spec/spec_helper.rb
|
162
|
+
homepage: https://github.com/pezra/hal-interpretation
|
163
|
+
licenses:
|
164
|
+
- MIT
|
165
|
+
metadata: {}
|
166
|
+
post_install_message:
|
167
|
+
rdoc_options: []
|
168
|
+
require_paths:
|
169
|
+
- lib
|
170
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - '>='
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: 1.9.3
|
175
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - '>='
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
requirements: []
|
181
|
+
rubyforge_project:
|
182
|
+
rubygems_version: 2.0.3
|
183
|
+
signing_key:
|
184
|
+
specification_version: 4
|
185
|
+
summary: Build models from HAL documents.
|
186
|
+
test_files:
|
187
|
+
- spec/hal_interpretation_spec.rb
|
188
|
+
- spec/spec_helper.rb
|