halibut 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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