halibut 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -1
- data/.gitmodules +3 -0
- data/.travis.yml +4 -4
- data/Gemfile +2 -2
- data/Gemfile.travis +11 -0
- data/README.md +83 -34
- data/Rakefile +2 -2
- data/TODO +2 -0
- data/halibut.gemspec +16 -11
- data/lib/halibut.rb +15 -3
- data/lib/halibut/adapter/json.rb +72 -0
- data/lib/halibut/adapter/xml.rb +88 -0
- data/lib/halibut/builder.rb +75 -0
- data/lib/halibut/hal/link.rb +81 -0
- data/lib/halibut/hal/resource.rb +74 -0
- data/lib/halibut/link_relation.rb +29 -0
- data/lib/halibut/relation_map.rb +24 -23
- data/lib/halibut/version.rb +1 -2
- data/spec/adapter/json_spec.rb +72 -0
- data/spec/adapter/xml_spec.rb +50 -0
- data/spec/builder_spec.rb +185 -0
- data/spec/fixtures/serialize.json +1 -1
- data/spec/link_relation_spec.rb +43 -0
- data/spec/link_spec.rb +13 -11
- data/spec/relation_map_spec.rb +7 -6
- data/spec/resource_spec.rb +23 -53
- data/spec/spec_helper.rb +4 -2
- metadata +78 -15
- data/lib/halibut/link.rb +0 -56
- data/lib/halibut/resource.rb +0 -94
data/.gitignore
CHANGED
data/.gitmodules
ADDED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/Gemfile.travis
ADDED
data/README.md
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
# Halibut
|
2
2
|
|
3
|
-
|
3
|
+
Halibut is a tiny gem that makes it easier to deal with the [HAL](http://stateless.co/hal_specification.html) format.
|
4
|
+
|
5
|
+
In providing tools to build, (de)serialize, and manipulate HAL resources,
|
6
|
+
Halibut has the following goals:
|
7
|
+
|
8
|
+
- Provide Ruby abstractions
|
9
|
+
- Clean, small API
|
10
|
+
- Easily composable, for use in other libraries
|
4
11
|
|
5
12
|
## Installation
|
6
13
|
|
@@ -18,56 +25,98 @@ Or install it yourself as:
|
|
18
25
|
|
19
26
|
## Usage
|
20
27
|
|
21
|
-
|
28
|
+
There are three ways to get a resource with halibut: manual, Builder, and JSON.
|
22
29
|
|
23
|
-
|
30
|
+
### Manual
|
24
31
|
|
25
32
|
```ruby
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
Halibut::HAL
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
+
require 'halibut'
|
34
|
+
|
35
|
+
# manually creating a resource
|
36
|
+
order = Halibut::HAL::Resource.new "/orders/123"
|
37
|
+
order.set_property "total", 30.00
|
38
|
+
order.set_property "currency", "USD"
|
39
|
+
order.set_property "status", "shipped"
|
40
|
+
|
41
|
+
resource = Halibut::HAL::Resource.new "/orders"
|
42
|
+
resource.add_link "find", "/orders{?id}", templated: true
|
43
|
+
resource.add_link "next", "/orders/1", "name" => 'hotdog'
|
44
|
+
resource.add_link "next", "/orders/9"
|
45
|
+
resource.set_property "currentlyProcessing", 14
|
46
|
+
resource.set_property "shippedToday", 20
|
47
|
+
resource.embed_resource "orders", order
|
48
|
+
```
|
33
49
|
|
34
|
-
Halibut::
|
35
|
-
|
50
|
+
### Halibut::Builder
|
51
|
+
```ruby
|
52
|
+
require 'halibut/builder'
|
36
53
|
|
37
|
-
Halibut::
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
54
|
+
builder = Halibut::Builder.new '/orders' do
|
55
|
+
property 'currentlyProcessing', 14
|
56
|
+
property 'shippedToday', 20
|
57
|
+
|
58
|
+
namespace 'th', 'http://things-db.com/{rel}'
|
59
|
+
|
60
|
+
link 'find', '/orders{?id}', templated: true
|
61
|
+
link 'next', '/orders/1', name: 'hotdog'
|
62
|
+
link 'next', '/orders/9'
|
63
|
+
|
64
|
+
link 'th:manufacturer', '/manufacturer/1'
|
65
|
+
link 'th:manufacturer', '/manufacturer/2'
|
66
|
+
link 'th:manufacturer', '/manufacturer/3'
|
67
|
+
end
|
43
68
|
|
44
|
-
|
45
|
-
it.attr "some_attribute", "The Value of the Attribute"
|
46
|
-
it.attr "another_attribute", "The Value of Another Attribute"
|
69
|
+
# alternatively
|
47
70
|
|
48
|
-
|
71
|
+
builder = Halibut::Builder.new '/orders' do
|
72
|
+
property 'currentlyProcessing', 14
|
73
|
+
property 'shippedToday', 20
|
74
|
+
|
75
|
+
namespace 'th', 'http://things-db.com/{rel}'
|
49
76
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
r.link "search", "/href/etc?search={term}", :templated => true, :title => "Embedded Resource"
|
77
|
+
link 'find', '/orderes{?id}', templated: true
|
78
|
+
relation 'next' do
|
79
|
+
link '/orders/1', name: 'hotdog'
|
80
|
+
link '/orders/9'
|
55
81
|
end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
r.link "search", "/href/more?search={term}", :templated => true, :title => "Embedded Resource"
|
82
|
+
relation 'th:manufacturer' do
|
83
|
+
link '/manufacturers/1'
|
84
|
+
link '/manufacturers/2'
|
85
|
+
link '/manufacturers/3'
|
61
86
|
end
|
62
87
|
end
|
63
|
-
hal.to_xml
|
64
88
|
|
65
|
-
|
89
|
+
resource = builder.resource
|
90
|
+
```
|
91
|
+
|
92
|
+
### JSON
|
93
|
+
```ruby
|
94
|
+
require 'halibut/adapter/json'
|
95
|
+
|
96
|
+
# converting to JSON
|
97
|
+
Halibut::Adapter::JSON.dump resource
|
98
|
+
|
99
|
+
# creating a resource from JSON
|
100
|
+
resource = Halibut::Adapter::JSON.load 'resource.json'
|
101
|
+
```
|
102
|
+
|
103
|
+
### XML
|
104
|
+
```ruby
|
105
|
+
require 'halibut/adapter/xml'
|
106
|
+
|
107
|
+
# converting to XML
|
108
|
+
# Coming soon…
|
109
|
+
|
110
|
+
# creating a resource from XML
|
111
|
+
resource = Halibut::Adapter::XML.load 'resource.xml'
|
66
112
|
```
|
67
113
|
|
68
114
|
## Contributing
|
69
115
|
|
70
116
|
1. Fork it
|
117
|
+
1. `git submodule update --init`
|
118
|
+
2. `bundle install`, optionally pass `--without-development` and use your
|
119
|
+
own tools
|
71
120
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
72
121
|
3. Commit your changes (`git commit -am 'Added some feature'`)
|
73
122
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/Rakefile
CHANGED
data/TODO
ADDED
data/halibut.gemspec
CHANGED
@@ -14,22 +14,27 @@ Gem::Specification.new do |gem|
|
|
14
14
|
gem.name = "halibut"
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Halibut::VERSION
|
17
|
-
|
17
|
+
|
18
|
+
gem.required_ruby_version = '~> 1.9.3'
|
19
|
+
|
18
20
|
gem.add_dependency "multi_json"
|
19
21
|
gem.add_dependency "nokogiri"
|
20
|
-
gem.add_dependency "
|
21
|
-
|
22
|
-
gem.add_development_dependency "
|
23
|
-
|
24
|
-
gem.add_development_dependency "pry"
|
25
|
-
gem.add_development_dependency "pry-
|
26
|
-
gem.add_development_dependency "pry-
|
27
|
-
|
28
|
-
|
29
|
-
gem.add_development_dependency "
|
22
|
+
gem.add_dependency "addressable"
|
23
|
+
|
24
|
+
gem.add_development_dependency "minitest", ">= 4.1"
|
25
|
+
|
26
|
+
gem.add_development_dependency "pry", ">= 0.9.10"
|
27
|
+
gem.add_development_dependency "pry-doc"
|
28
|
+
gem.add_development_dependency "pry-stack_explorer"
|
29
|
+
gem.add_development_dependency "pry-coolline", "0.1.5"
|
30
|
+
gem.add_development_dependency "pry-rescue", "0.13"
|
31
|
+
gem.add_development_dependency "hash-differ"
|
32
|
+
|
30
33
|
gem.add_development_dependency "guard"
|
31
34
|
gem.add_development_dependency "guard-bundler"
|
32
35
|
gem.add_development_dependency "guard-minitest"
|
33
36
|
|
37
|
+
gem.add_development_dependency "rb-fsevent"
|
34
38
|
gem.add_development_dependency "terminal-notifier-guard"
|
39
|
+
|
35
40
|
end
|
data/lib/halibut.rb
CHANGED
@@ -2,8 +2,20 @@ require "halibut/version"
|
|
2
2
|
|
3
3
|
# Halibut is the main namespace
|
4
4
|
module Halibut
|
5
|
+
autoload :Builder, 'halibut/builder'
|
6
|
+
|
7
|
+
autoload :RelationMap, 'halibut/relation_map'
|
8
|
+
end
|
9
|
+
|
10
|
+
# The Adapter namespace contains classes that aid in the
|
11
|
+
# mapping of HAL Resources into a specific format.
|
12
|
+
module Halibut::Adapter
|
13
|
+
autoload :JSON, 'halibut/adapter/json'
|
14
|
+
autoload :XML, 'halibut/adapter/xml'
|
5
15
|
end
|
6
16
|
|
7
|
-
|
8
|
-
|
9
|
-
|
17
|
+
# Halibut::HAL contains the domain objects that reflect the HAL specs.
|
18
|
+
module Halibut::HAL
|
19
|
+
autoload :Link, 'halibut/hal/link'
|
20
|
+
autoload :Resource, 'halibut/hal/resource'
|
21
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module Halibut::Adapter
|
4
|
+
|
5
|
+
module JSON
|
6
|
+
def self.extended(base)
|
7
|
+
base.extend InstanceMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.load(json)
|
11
|
+
ResourceExtractor.new(json).resource
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.dump(resource)
|
15
|
+
MultiJson.dump resource.to_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
module InstanceMethods
|
20
|
+
def to_json
|
21
|
+
MultiJson.dump self.to_hash
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class ResourceExtractor
|
26
|
+
def initialize(json)
|
27
|
+
@halibut = Halibut::HAL::Resource.new
|
28
|
+
json = MultiJson.load(json)
|
29
|
+
|
30
|
+
links = json.delete '_links'
|
31
|
+
resources = json.delete '_embedded'
|
32
|
+
properties = json
|
33
|
+
|
34
|
+
properties and extract_properties properties
|
35
|
+
links and extract_links links
|
36
|
+
resources and extract_resources resources
|
37
|
+
end
|
38
|
+
|
39
|
+
def resource
|
40
|
+
@halibut
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def extract_properties(properties)
|
45
|
+
properties.each_pair do |property, value|
|
46
|
+
@halibut.set_property(property, value)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def extract_links(links)
|
51
|
+
links.each do |relation,values|
|
52
|
+
links = ([] << values).flatten
|
53
|
+
|
54
|
+
links.each do |attrs|
|
55
|
+
href = attrs.delete 'href'
|
56
|
+
@halibut.add_link(relation, href, attrs)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def extract_resources(resources)
|
62
|
+
resources.each do |relation,values|
|
63
|
+
embeds = ([] << values).flatten
|
64
|
+
|
65
|
+
embeds.map {|embed| MultiJson.dump embed }
|
66
|
+
.map {|embed| Halibut::Adapter::JSON.load embed }
|
67
|
+
.each {|embed| @halibut.embed_resource(relation, embed) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Halibut::Adapter
|
4
|
+
|
5
|
+
module XML
|
6
|
+
def self.extended(base)
|
7
|
+
base.extend InstanceMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.load(xml)
|
11
|
+
ResourceExtractor.new(xml).resource
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.dump(resource)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
module InstanceMethods
|
19
|
+
def to_xml
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class ResourceExtractor
|
24
|
+
def initialize(xml)
|
25
|
+
xml = Nokogiri::XML(xml)
|
26
|
+
|
27
|
+
@document = xml.root
|
28
|
+
@halibut = Halibut::HAL::Resource.new extract_self_link
|
29
|
+
|
30
|
+
# binding.pry
|
31
|
+
|
32
|
+
extract_curie
|
33
|
+
extract_properties
|
34
|
+
extract_links
|
35
|
+
extract_resources
|
36
|
+
end
|
37
|
+
|
38
|
+
def resource
|
39
|
+
@halibut
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def extract_self_link
|
44
|
+
@document.attr 'href'
|
45
|
+
end
|
46
|
+
|
47
|
+
def extract_curie
|
48
|
+
@document.namespace_scopes
|
49
|
+
.reject {|ns| ns.prefix.eql? 'xsi' }
|
50
|
+
.each do |ns|
|
51
|
+
@halibut.add_link 'curie', ns.href, name: ns.prefix
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def extract_properties
|
56
|
+
properties = @document.xpath '/resource/*[not(self::link) and not(self::resource)]'
|
57
|
+
|
58
|
+
properties.each do |property|
|
59
|
+
@halibut.set_property property.name, property.content
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def extract_links
|
64
|
+
links = @document.xpath('/resource/link')
|
65
|
+
|
66
|
+
links.each do |link|
|
67
|
+
@halibut.add_link link.attribute('rel').value,
|
68
|
+
link.attribute('href').value,
|
69
|
+
extract_link_options(link)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# In case there are no options on the link, it returns an empty hash
|
74
|
+
def extract_link_options(link)
|
75
|
+
link.attributes.reject {|k,v| k=='rel' || k=='href' }
|
76
|
+
.map {|k,v| { k => v.value } }
|
77
|
+
.reduce(:merge) || {}
|
78
|
+
end
|
79
|
+
|
80
|
+
def extract_resources
|
81
|
+
@document.xpath('/resource/resource')
|
82
|
+
.map {|r| [] << r['rel'] << ResourceExtractor.new(r.to_xml).resource }
|
83
|
+
.each {|rel,res| @halibut.embed_resource rel, res }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'halibut/hal/resource'
|
2
|
+
|
3
|
+
module Halibut
|
4
|
+
|
5
|
+
# Builder provides a very thin wrapper around creating a HAL resource.
|
6
|
+
class Builder
|
7
|
+
attr_accessor :resource
|
8
|
+
|
9
|
+
def initialize(href=nil, &blk)
|
10
|
+
@resource = Halibut::HAL::Resource.new href
|
11
|
+
|
12
|
+
RootContext.new(@resource, &blk)
|
13
|
+
end
|
14
|
+
|
15
|
+
def respond_to?(meth, *args)
|
16
|
+
RootContext.new(@resource).respond_to? meth
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing meth, *args
|
20
|
+
RootContext.new(@resource).send meth, *args
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
class RootContext
|
25
|
+
extend Forwardable
|
26
|
+
|
27
|
+
def_delegator :@resource, :set_property, :property
|
28
|
+
def_delegator :@resource, :add_link, :link
|
29
|
+
|
30
|
+
def initialize(resource, &blk)
|
31
|
+
@resource = resource
|
32
|
+
|
33
|
+
instance_eval(&blk) if block_given?
|
34
|
+
end
|
35
|
+
|
36
|
+
def namespace(name, href)
|
37
|
+
@resource.add_link("curie", href, name: name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def resource(rel, href=nil, &blk)
|
41
|
+
embedded = Halibut::Builder.new(href, &blk)
|
42
|
+
|
43
|
+
@resource.embed_resource(rel, embedded.resource)
|
44
|
+
end
|
45
|
+
|
46
|
+
def relation(rel, &blk)
|
47
|
+
RelationContext.new(@resource, rel, &blk)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
class RelationContext
|
53
|
+
|
54
|
+
def initialize(resource, rel, &blk)
|
55
|
+
@resource = resource
|
56
|
+
@rel = rel
|
57
|
+
|
58
|
+
instance_eval(&blk) if block_given?
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def link(href, opts={})
|
63
|
+
@resource.add_link(@rel, href, opts)
|
64
|
+
end
|
65
|
+
|
66
|
+
def resource(href=nil, &blk)
|
67
|
+
embedded = Halibut::Builder.new(href, &blk)
|
68
|
+
|
69
|
+
@resource.embed_resource(@rel, embedded.resource)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|