halibut 0.4.1 → 0.5.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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MWYxOTQ4OTZiYzU2ZWQyZjk1OTE3NzVmMTA3OTVkMzBhMTQ5NmIyYQ==
4
+ ZDNlOWE2ZGIyZDI1ZjUzM2UzY2E2NjBhYTIwNGExYTYyNGM5Njk5ZQ==
5
5
  data.tar.gz: !binary |-
6
- NDYzNzNhZjdkZWYxOTg1NjQzOWEzYWQwM2UzZTk0N2FmOWNhYTc1Zg==
6
+ ZjViM2MwNzNlNmZlYmQxYzI2ZTQwMTUyM2UwYThmMjI5ZWY1NzlkOA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- OTdjNmE0Mzg3MDlmZmJlMzc2ZGI1YzE3ZTc4OGZiMjdmNDc2ZDhhMzUwOTM4
10
- ODA4NDhkNGQ1MjlkMjhjNzY3N2MyMzM4YWVmMjdiM2Q1YmY2NGM0MmM3OGYy
11
- Y2U1NDNkMjNmOGNkYjBjYmUzYjQxNTI2ZGU2ZWM0MWU4Y2MzY2I=
9
+ ODI1OGE1NDEyN2RiZDUzYTlhMTQzYmM5ZWIxODdlNTdjNDEzZWU0NDA5NGM0
10
+ NTI2NjQxMWY4YmRiYmNiOGJhYTY5MDVlMmE3ZDBhMmNiMGQ5NTkxNzI0OWFm
11
+ MjBkMDY3ZjBhZmVjZDk1NWEyZWMxYmQwM2JlOWMxYTc1NmJlOTE=
12
12
  data.tar.gz: !binary |-
13
- ZDAxZTlhYjRjNzEyOTM4ODg3ZmJmYzczMGU1YTVhMDBlNmZmNzcwNWYyNzhk
14
- NmU4MTJmNWVjYTQzNWE5OTEzMDUzYjNkYzcxODk5ZjM4ZjQ5YTNjMzQyYTZk
15
- YzYwOGU2ZjMxOGIzMWJhZWFkZDY5YWVmMGM1NTYwZTk0NmE1OGE=
13
+ OTFmODkyOWJmM2U5MzA0MDQ0NDBiZmUzZGNmZTY0Y2UwOWIwZWM1NTAwYzVi
14
+ YjZkMjRjZGM2OGYwNTI0MTJmY2RmOTE0MjFkYTYzNmY4ZDU2MDFhZDk5M2I1
15
+ Mzc4NTIyM2U3NDE5NTYzMjY3ZmQwYWI5ZDUzNDA0MWY2ZmJjMGI=
data/.gitignore CHANGED
@@ -23,4 +23,7 @@ doc/
23
23
  .DS_Store
24
24
 
25
25
  # Rubinius
26
+
27
+ # IntelliJ
28
+ .idea
26
29
  .rbx
@@ -6,7 +6,7 @@ rvm:
6
6
  - ruby-head
7
7
  - jruby-19mode
8
8
  - jruby-head
9
- - rbx-2.1.1
9
+ - rbx-2
10
10
  deploy:
11
11
  provider: rubygems
12
12
  api_key:
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Halibut [![endorse](http://api.coderwall.com/locks/endorsecount.png)](http://coderwall.com/locks) [![Stories in Ready](http://badge.waffle.io/locks/halibut.png)](http://waffle.io/locks/halibut)
1
+ # Halibut [![Stories in Ready](http://badge.waffle.io/locks/halibut.png)](http://waffle.io/locks/halibut) [![Gitter chat](https://badges.gitter.im/locks/halibut.png)](https://gitter.im/locks/halibut)
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/halibut.png)](http://badge.fury.io/rb/halibut)
4
4
  [![Build Status](https://secure.travis-ci.org/locks/halibut.png?branch=master)](https://travis-ci.org/locks/halibut)
@@ -44,13 +44,19 @@ order.set_property "total", 30.00
44
44
  order.set_property "currency", "USD"
45
45
  order.set_property "status", "shipped"
46
46
 
47
+ payment = Halibut::Core::Resource.new "/orders/123/payment"
48
+ payment.set_property "method", "credit"
49
+ payment.set_property "amount", "30.00"
50
+ payment.set_property "auth_code", "ABZ127"
51
+
47
52
  resource = Halibut::Core::Resource.new "/orders"
48
53
  resource.add_link "find", "/orders{?id}", templated: true
49
54
  resource.add_link "next", "/orders/1", "name" => 'hotdog'
50
55
  resource.add_link "next", "/orders/9"
51
56
  resource.set_property "currentlyProcessing", 14
52
57
  resource.set_property "shippedToday", 20
53
- resource.embed_resource "orders", order
58
+ resource.embed_resource "payment", payment
59
+ resource.add_embed_resource "orders", order
54
60
  ```
55
61
 
56
62
  ### Halibut::Builder
@@ -7,6 +7,7 @@ Gem::Specification.new do |gem|
7
7
  gem.description = %q{Toolkit to work with HAL}
8
8
  gem.summary = %q{A HAL parser and builder for use in Hypermedia APIs}
9
9
  gem.homepage = "http://locks.github.com/halibut"
10
+ gem.license = "MIT"
10
11
 
11
12
  gem.files = `git ls-files`.split($\)
12
13
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -10,6 +10,8 @@ require 'halibut/builder'
10
10
  module Halibut::Adapter; end
11
11
 
12
12
  require 'halibut/adapter/json'
13
+ Halibut.extend Halibut::Adapter::JSON::ConvenienceMethods
14
+
13
15
 
14
16
  # Halibut::Core contains the domain objects that reflect the HAL specs.
15
17
  module Halibut::Core; end
@@ -111,5 +111,11 @@ module Halibut::Adapter
111
111
  end
112
112
  end
113
113
  end
114
+
115
+ module ConvenienceMethods
116
+ def parse_json(json_str_or_io)
117
+ Halibut::Adapter::JSON.parse(json_str_or_io)
118
+ end
119
+ end
114
120
  end
115
121
  end
@@ -9,10 +9,10 @@ module Halibut
9
9
  #
10
10
  # @param [String] href
11
11
  # @param [Proc] blk
12
- def initialize(href=nil, &blk)
12
+ def initialize(href=nil, &resource_definition)
13
13
  @resource = Halibut::Core::Resource.new href
14
14
 
15
- RootContext.new(@resource, &blk)
15
+ RootContext.new(@resource, &resource_definition)
16
16
  end
17
17
 
18
18
  # Returns the resource built.
@@ -28,10 +28,10 @@ module Halibut
28
28
  #
29
29
  class RootContext
30
30
 
31
- def initialize(resource, &blk)
31
+ def initialize(resource, &resource_definition)
32
32
  @resource = resource
33
33
 
34
- instance_eval(&blk) if block_given?
34
+ instance_eval(&resource_definition) if block_given?
35
35
  end
36
36
 
37
37
  # Sets a property on the resource.
@@ -78,8 +78,8 @@ module Halibut
78
78
  # @param [String] rel Embedded resource relation to the parent resource
79
79
  # @param [String] href URI to the resource itself
80
80
  # @param [Proc] blk Instructions to construct the embedded resource
81
- def resource(rel, href=nil, &blk)
82
- embedded = Halibut::Builder.new(href, &blk)
81
+ def resource(rel, href=nil, &embedded_definition)
82
+ embedded = Halibut::Builder.new(href, &embedded_definition)
83
83
 
84
84
  @resource.embed_resource(rel, embedded.resource)
85
85
  end
@@ -101,26 +101,26 @@ module Halibut
101
101
  # @param [String,Symbol] rel
102
102
  # @param [Proc] blk Instructions to be executed in the relation
103
103
  # context
104
- def relation(rel, &blk)
105
- RelationContext.new(@resource, rel, &blk)
104
+ def relation(rel, &relation_definition)
105
+ RelationContext.new(@resource, rel, &relation_definition)
106
106
  end
107
107
  end
108
108
 
109
109
  class RelationContext
110
110
 
111
- def initialize(resource, rel, &blk)
111
+ def initialize(resource, rel, &relation_definition)
112
112
  @resource = resource
113
113
  @rel = rel
114
114
 
115
- instance_eval(&blk) if block_given?
115
+ instance_eval(&relation_definition) if block_given?
116
116
  end
117
117
 
118
118
  def link(href, opts={})
119
119
  @resource.tap {|obj| obj.add_link(@rel, href, opts) }
120
120
  end
121
121
 
122
- def resource(href=nil, &blk)
123
- embedded = Halibut::Builder.new(href, &blk)
122
+ def resource(href=nil, &embedded_definition)
123
+ embedded = Halibut::Builder.new(href, &embedded_definition)
124
124
 
125
125
  @resource.embed_resource(@rel, embedded.resource)
126
126
  end
@@ -8,8 +8,11 @@ module Halibut::Core
8
8
 
9
9
  def_delegators :@relations, :[], :empty?, :==, :fetch
10
10
 
11
- def initialize
11
+ DEFAULT_OPTIONS = { single_item_arrays: false }
12
+
13
+ def initialize(options = {})
12
14
  @relations = {}
15
+ @options = DEFAULT_OPTIONS.merge(options)
13
16
  end
14
17
 
15
18
  # Adds an object to a relation.
@@ -25,6 +28,10 @@ module Halibut::Core
25
28
  # @param [String] relation relation that the object belongs to
26
29
  # @param [Object] item the object to add to the relation
27
30
  def add(relation, item)
31
+ unless item.respond_to?(:to_hash)
32
+ raise ArgumentError.new('only items that can be converted to hashes with #to_hash are permitted')
33
+ end
34
+
28
35
  @relations[relation] = @relations.fetch(relation, []) << item
29
36
  end
30
37
 
@@ -37,10 +44,21 @@ module Halibut::Core
37
44
  def to_hash
38
45
  @relations.each_with_object({}) do |(rel,val), obj|
39
46
  rel = rel.to_s
40
- val = val.length == 1 ? val.first.to_hash : val.map(&:to_hash)
41
47
 
42
- obj[rel] = val
48
+ hashed_val = val.map(&:to_hash)
49
+ if val.length == 1 && !single_item_arrays?
50
+ hashed_val = val.first.to_hash
51
+ end
52
+
53
+ obj[rel] = hashed_val
43
54
  end
44
55
  end
56
+
57
+ # Returns true if the relation map is configured to always to
58
+ # permit single arrays when to_hash is called. The default behavior
59
+ # is to convert single item arrays into instances
60
+ def single_item_arrays?
61
+ @options[:single_item_arrays]
62
+ end
45
63
  end
46
64
  end
@@ -38,9 +38,10 @@ module Halibut::Core
38
38
  #
39
39
  # @param [String] href Link that will be added to the self relation.
40
40
  def initialize(href=nil, properties={}, links={}, embedded={})
41
- @namespaces = RelationMap.new
42
- @links = RelationMap.new
43
- @embedded = RelationMap.new
41
+ @namespaces = RelationMap.new
42
+ @links = RelationMap.new
43
+ @embedded = RelationMap.new
44
+ @embedded_arrays = RelationMap.new(single_item_arrays: true)
44
45
  @properties = {}
45
46
 
46
47
  add_link('self', href) if href
@@ -123,20 +124,37 @@ module Halibut::Core
123
124
 
124
125
  # Embeds resource in relation
125
126
  #
127
+ # To embed many resources, call #add_embedded_resource with each item
128
+ #
126
129
  # @param [String] relation relation
127
130
  # @param [Resource] resource resource to embed
128
131
  def embed_resource(relation, resource)
132
+ warn 'Calling Halibut::Core::Resource#embed_resource for populating an array is deprecated. Use Halibut::Core::Resource#add_embeded_resource instead.' if @embedded[relation]
129
133
  @embedded.add relation, resource
130
134
  end
131
135
 
136
+ # Embeds resource in a relation array
137
+ #
138
+ # To embed a single object, see #embed_resource.
139
+ #
140
+ # @param [String] relation relation
141
+ # @param [Resource] resource resource to embed
142
+ def add_embedded_resource(relation, resource)
143
+ @embedded_arrays.add relation, resource
144
+ end
145
+
132
146
  # Hash representation of the resource.
133
147
  # Will ommit links and embedded keys if they're empty
134
148
  #
135
149
  # @return [Hash] hash representation of the resource
136
150
  def to_hash
137
151
  {}.merge(@properties).tap do |h|
138
- h['_links'] = {}.merge @links unless @links.empty?
139
- h['_embedded'] = {}.merge @embedded unless @embedded.empty?
152
+ h['_links'] = {}.merge @links unless @links.empty?
153
+
154
+ combined_embedded = {}
155
+ combined_embedded.merge! @embedded unless @embedded.empty?
156
+ combined_embedded.merge! @embedded_arrays unless @embedded_arrays.empty?
157
+ h['_embedded'] = combined_embedded unless combined_embedded.empty?
140
158
  end
141
159
  end
142
160
 
@@ -1,4 +1,4 @@
1
1
  module Halibut
2
2
  # Semver version (I'll try, pinkie promise)
3
- VERSION = "0.4.1"
3
+ VERSION = "0.5.0"
4
4
  end
@@ -16,6 +16,27 @@ describe Halibut::Adapter::JSON do
16
16
  MultiJson.load(subject).must_equal MultiJson.load(json)
17
17
  end
18
18
 
19
+ it "serializes to JSON with links and embedded resources" do
20
+ json = load_json "serialize"
21
+
22
+ order = Halibut::Core::Resource.new "/orders/123"
23
+ order.set_property "total", 30.00
24
+ order.set_property "currency", "USD"
25
+ order.set_property "status", "shipped"
26
+
27
+ resource = Halibut::Core::Resource.new "/orders"
28
+ resource.add_link "find", "/orders{?id}", templated: true
29
+ resource.add_link "next", "/orders/1", "name" => 'hotdog'
30
+ resource.add_link "next", "/orders/9"
31
+ resource.set_property "currentlyProcessing", 14
32
+ resource.set_property "shippedToday", 20
33
+ resource.add_embedded_resource "orders", order
34
+
35
+ subject = Halibut::Adapter::JSON.dump resource
36
+
37
+ MultiJson.load(subject).must_equal MultiJson.load(json)
38
+ end
39
+
19
40
  it "deserializes from JSON" do
20
41
  subject = Halibut::Adapter::JSON.parse(load_json "serialize")
21
42
 
@@ -1,6 +1,4 @@
1
1
  require_relative 'spec_helper'
2
-
3
-
4
2
  require 'hash'
5
3
 
6
4
  describe Halibut::Builder do
@@ -9,22 +9,72 @@ describe Halibut::Core::RelationMap do
9
9
  subject.must_be_empty
10
10
  end
11
11
 
12
- it "has a single item per relation" do
13
- subject.add 'first' , 'first'
14
- subject.add 'second', 'second'
12
+ describe '#add' do
13
+ it "has a single item per relation" do
14
+ subject.add 'first' , { value: 'first' }
15
+ subject.add 'second', { value: 'second' }
15
16
 
16
- subject['first'].first.must_equal 'first'
17
- subject['second'].last.must_equal 'second'
17
+ subject['first'].first[:value].must_equal 'first'
18
+ subject['second'].last[:value].must_equal 'second'
18
19
 
19
- end
20
+ end
21
+
22
+ it "has various items per relation" do
23
+ subject.add 'first', { value: 'first' }
24
+ subject.add 'first', { value: 'second' }
20
25
 
21
- it "has various items per relation" do
22
- subject.add 'first', 'first'
23
- subject.add 'first', 'second'
26
+ subject['first'].length.must_equal 2
27
+ subject['first'].first[:value].must_equal 'first'
28
+ subject['first'].last[:value].must_equal 'second'
29
+ end
24
30
 
25
- subject['first'].length.must_equal 2
26
- subject['first'].first.must_equal 'first'
27
- subject['first'].last.must_equal 'second'
31
+ # todo: throw an exception if add receives a value that does not respond to to_hash
32
+ it 'throws an exception if item does not respond to #to_hash' do
33
+ assert_raises(ArgumentError) do
34
+ subject.add 'first', 'not-hashable'
35
+ end
36
+ end
28
37
  end
29
38
 
30
- end
39
+ describe '#to_hash' do
40
+ describe 'when single item arrays become objects' do
41
+ it 'generates single item relations correctly' do
42
+ subject.add('person', { name: 'bob' })
43
+
44
+ subject.to_hash['person'][:name].must_equal 'bob'
45
+ end
46
+
47
+ it 'generates multi item relations correctly' do
48
+ subject.add('person', { name: 'bob' })
49
+ subject.add('person', { name: 'floyd' })
50
+
51
+ hashed_people = subject.to_hash['person']
52
+ hashed_people.length.must_equal 2
53
+ hashed_people[0][:name].must_equal 'bob'
54
+ hashed_people[1][:name].must_equal 'floyd'
55
+ end
56
+ end
57
+
58
+ describe 'when single item arrays stay arrays' do
59
+ subject { Halibut::Core::RelationMap.new(single_item_arrays: true) }
60
+
61
+ it 'generates single item relations correctly' do
62
+ subject.add('person', { name: 'bob' })
63
+
64
+ hashed_people = subject.to_hash['person']
65
+ hashed_people.length.must_equal 1
66
+ hashed_people[0][:name].must_equal 'bob'
67
+ end
68
+
69
+ it 'generates multi item relations correctly' do
70
+ subject.add('person', { name: 'bob' })
71
+ subject.add('person', { name: 'floyd' })
72
+
73
+ hashed_people = subject.to_hash['person']
74
+ hashed_people.length.must_equal 2
75
+ hashed_people[0][:name].must_equal 'bob'
76
+ hashed_people[1][:name].must_equal 'floyd'
77
+ end
78
+ end
79
+ end
80
+ end
@@ -88,15 +88,50 @@ describe Halibut::Core::Resource do
88
88
  let(:res2) { Halibut::Core::Resource.new "http://secnd-resource.com" }
89
89
 
90
90
  it "no embedded resource" do
91
- subject.embedded.must_be_empty
91
+ subject.to_hash['_embedded'].must_be_nil
92
92
  end
93
93
 
94
94
  it "has embedded resource" do
95
- subject.embed_resource 'users', res1
96
- subject.embed_resource 'users', res2
95
+ subject.embed_resource 'user', res1
96
+
97
+ subject.to_hash['_embedded']['user'].must_equal res1.to_hash
98
+ end
99
+
100
+ it "deprecates calling embed_resource twice for the same relation" do
101
+ subject.embed_resource 'user', res1
102
+
103
+ # this call intentionally generates a deprecation warning
104
+ subject.embed_resource 'user', res2
105
+
106
+ subject.to_hash['_embedded']['user'].length.must_equal 2
107
+ subject.to_hash['_embedded']['user'].first.must_equal res1.to_hash
108
+ subject.to_hash['_embedded']['user'].last.must_equal res2.to_hash
109
+ end
110
+ end
111
+
112
+ describe "Embedded arrays" do
113
+ subject { Halibut::Core::Resource.new }
114
+ let(:res1) { Halibut::Core::Resource.new "http://first-resource.com" }
115
+ let(:res2) { Halibut::Core::Resource.new "http://secnd-resource.com" }
116
+
117
+ it "has no embedded arrays" do
118
+ subject.embedded.must_be_empty
119
+ end
120
+
121
+ it "has a resource embedded into an array" do
122
+ subject.add_embedded_resource 'users', res1
123
+
124
+ subject.to_hash['_embedded']['users'].length.must_equal 1
125
+ subject.to_hash['_embedded']['users'].first.must_equal res1.to_hash
126
+ end
127
+
128
+ it "has multiple resources embedded into an array" do
129
+ subject.add_embedded_resource 'users', res1
130
+ subject.add_embedded_resource 'users', res2
97
131
 
98
- subject.embedded['users'].first.must_equal res1
99
- subject.embedded['users'].last.must_equal res2
132
+ subject.to_hash['_embedded']['users'].length.must_equal 2
133
+ subject.to_hash['_embedded']['users'].first.must_equal res1.to_hash
134
+ subject.to_hash['_embedded']['users'].last.must_equal res2.to_hash
100
135
  end
101
136
  end
102
137
 
@@ -0,0 +1,25 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Halibut do
4
+ describe ".parse_json" do
5
+ describe "(valid_hal_json_str)" do
6
+ it "returns a Resource" do
7
+ Halibut.parse_json("{}").must_be_kind_of Halibut::Core::Resource
8
+ end
9
+ end
10
+
11
+ describe "(valid_hal_json_io)" do
12
+ it "returns a Resource" do
13
+ Halibut.parse_json(StringIO.new("{}")).must_be_kind_of Halibut::Core::Resource
14
+ end
15
+ end
16
+
17
+ describe "(invalid_json)" do
18
+ it "raises exception" do
19
+ proc { Halibut.parse_json StringIO.new("wat") }.must_raise MultiJson::LoadError
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: halibut
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ricardo Mendes
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-17 00:00:00.000000000 Z
11
+ date: 2014-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -102,10 +102,12 @@ files:
102
102
  - spec/fixtures/minimal.json
103
103
  - spec/fixtures/serialize.json
104
104
  - spec/fixtures/simple.json
105
+ - spec/halibut_spec.rb
105
106
  - spec/link_relation_spec.rb
106
107
  - spec/spec_helper.rb
107
108
  homepage: http://locks.github.com/halibut
108
- licenses: []
109
+ licenses:
110
+ - MIT
109
111
  metadata: {}
110
112
  post_install_message:
111
113
  rdoc_options: []
@@ -123,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
123
125
  version: '0'
124
126
  requirements: []
125
127
  rubyforge_project:
126
- rubygems_version: 2.1.11
128
+ rubygems_version: 2.2.2
127
129
  signing_key:
128
130
  specification_version: 4
129
131
  summary: A HAL parser and builder for use in Hypermedia APIs
@@ -138,5 +140,6 @@ test_files:
138
140
  - spec/fixtures/minimal.json
139
141
  - spec/fixtures/serialize.json
140
142
  - spec/fixtures/simple.json
143
+ - spec/halibut_spec.rb
141
144
  - spec/link_relation_spec.rb
142
145
  - spec/spec_helper.rb