hyperclient 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +2 -0
  4. data/.rubocop_todo.yml +55 -0
  5. data/.travis.yml +2 -0
  6. data/CHANGELOG.md +33 -10
  7. data/CONTRIBUTING.md +117 -0
  8. data/Gemfile +2 -1
  9. data/Guardfile +6 -6
  10. data/LICENSE +2 -2
  11. data/README.md +138 -0
  12. data/Rakefile +5 -3
  13. data/UPGRADING.md +37 -0
  14. data/examples/splines_api.rb +22 -0
  15. data/features/steps/api_navigation.rb +8 -8
  16. data/features/steps/default_config.rb +6 -6
  17. data/features/support/api.rb +4 -4
  18. data/features/support/fixtures.rb +1 -1
  19. data/hyperclient.gemspec +9 -8
  20. data/lib/faraday/connection.rb +2 -2
  21. data/lib/hyperclient.rb +1 -1
  22. data/lib/hyperclient/attributes.rb +1 -1
  23. data/lib/hyperclient/collection.rb +3 -3
  24. data/lib/hyperclient/curie.rb +49 -0
  25. data/lib/hyperclient/entry_point.rb +6 -4
  26. data/lib/hyperclient/link.rb +70 -58
  27. data/lib/hyperclient/link_collection.rb +36 -11
  28. data/lib/hyperclient/resource.rb +49 -18
  29. data/lib/hyperclient/resource_collection.rb +2 -1
  30. data/lib/hyperclient/version.rb +1 -1
  31. data/test/fixtures/element.json +12 -1
  32. data/test/hyperclient/attributes_test.rb +2 -2
  33. data/test/hyperclient/collection_test.rb +6 -7
  34. data/test/hyperclient/curie_test.rb +34 -0
  35. data/test/hyperclient/entry_point_test.rb +3 -2
  36. data/test/hyperclient/link_collection_test.rb +26 -5
  37. data/test/hyperclient/link_test.rb +111 -86
  38. data/test/hyperclient/resource_collection_test.rb +2 -2
  39. data/test/hyperclient/resource_test.rb +67 -30
  40. data/test/test_helper.rb +2 -2
  41. metadata +54 -39
  42. data/Gemfile.lock +0 -112
  43. data/MIT-LICENSE +0 -20
  44. data/Readme.md +0 -180
  45. data/examples/cyberscore.rb +0 -76
  46. data/examples/hal_shop.rb +0 -53
  47. data/lib/faraday/request/digest_authentication.rb +0 -85
  48. data/test/faraday/digest_authentication_test.rb +0 -41
@@ -1,5 +1,6 @@
1
1
  require 'hyperclient/collection'
2
2
  require 'hyperclient/link'
3
+ require 'hyperclient/curie'
3
4
 
4
5
  module Hyperclient
5
6
  # Public: A wrapper class to easily acces the links in a Resource.
@@ -12,30 +13,54 @@ module Hyperclient
12
13
  class LinkCollection < Collection
13
14
  # Public: Initializes a LinkCollection.
14
15
  #
15
- # collection - The Hash with the links.
16
+ # collection - The Hash with the links.
17
+ # curies - Link curies.
16
18
  # entry_point - The EntryPoint object to inject the configuration.
17
- def initialize(collection, entry_point)
18
- raise "Invalid response for LinkCollection. The response was: #{collection.inspect}" if collection && !collection.respond_to?(:collect)
19
+ def initialize(collection, curies, entry_point)
20
+ fail "Invalid response for LinkCollection. The response was: #{collection.inspect}" if collection && !collection.respond_to?(:collect)
19
21
 
20
- @collection = (collection || {}).inject({}) do |hash, (name, link)|
21
- hash.update(name => build_link(link, entry_point))
22
+ @curies = (curies || {}).reduce({}) do |hash, curie_hash|
23
+ curie = build_curie(curie_hash, entry_point)
24
+ hash.update(curie.name => curie)
25
+ end
26
+
27
+ @collection = (collection || {}).reduce({}) do |hash, (name, link)|
28
+ hash.update(name => build_link(name, link, @curies, entry_point))
22
29
  end
23
30
  end
24
31
 
25
32
  private
33
+
26
34
  # Internal: Creates links from the response hash.
27
35
  #
28
36
  # link_or_links - A Hash or an Array of hashes with the links to build.
29
- # entry_point - The EntryPoint object to inject the configuration.
37
+ # entry_point - The EntryPoint object to inject the configuration.
38
+ # curies - Optional curies for templated links.
30
39
  #
31
40
  # Returns a Link or an array of Links when given an Array.
32
- def build_link(link_or_links, entry_point)
41
+ def build_link(name, link_or_links, curies, entry_point)
33
42
  return unless link_or_links
34
- return Link.new(link_or_links, entry_point) unless link_or_links.respond_to?(:to_ary)
35
-
36
- link_or_links.collect do |link|
37
- build_link(link, entry_point)
43
+ if link_or_links.respond_to?(:to_ary)
44
+ link_or_links.map do |link|
45
+ build_link(name, link, curies, entry_point)
46
+ end
47
+ elsif (curie_parts = /(?<ns>[^:]+):(?<short_name>.+)/.match(name))
48
+ curie = curies[curie_parts[:ns]]
49
+ link_or_links['href'] = curie.expand(link_or_links['href']) if curie
50
+ Link.new(name, link_or_links, entry_point)
51
+ else
52
+ Link.new(name, link_or_links, entry_point)
38
53
  end
39
54
  end
55
+
56
+ # Internal: Creates a curie from the response hash.
57
+ #
58
+ # curie_hash - A Hash with the curie.
59
+ # entry_point - The EntryPoint object to inject the configuration.
60
+ #
61
+ # Returns a Link or an array of Links when given an Array.
62
+ def build_curie(curie_hash, entry_point)
63
+ Curie.new(curie_hash, entry_point)
64
+ end
40
65
  end
41
66
  end
@@ -10,53 +10,84 @@ module Hyperclient
10
10
  extend Forwardable
11
11
 
12
12
  # Public: Returns the attributes of the Resource as Attributes.
13
- attr_reader :attributes
13
+ attr_reader :_attributes
14
14
 
15
15
  # Public: Returns the links of the Resource as a LinkCollection.
16
- attr_reader :links
16
+ attr_reader :_links
17
17
 
18
18
  # Public: Returns the embedded resource of the Resource as a
19
19
  # ResourceCollection.
20
- attr_reader :embedded
20
+ attr_reader :_embedded
21
21
 
22
22
  # Public: Returns the response object for the HTTP request that created this
23
23
  # resource, if one exists.
24
- attr_reader :response
24
+ attr_reader :_response
25
25
 
26
26
  # Public: Delegate all HTTP methods (get, post, put, delete, options and
27
27
  # head) to its self link.
28
- def_delegators :self_link, :get, :post, :put, :delete, :options, :head
28
+ def_delegators :_self_link, :_get, :_post, :_put, :_delete, :_options, :_head
29
29
 
30
30
  # Public: Initializes a Resource.
31
31
  #
32
32
  # representation - The hash with the HAL representation of the Resource.
33
33
  # entry_point - The EntryPoint object to inject the configutation.
34
- def initialize(representation, entry_point, response=nil)
34
+ def initialize(representation, entry_point, response = nil)
35
35
  representation = representation ? representation.dup : {}
36
- @links = LinkCollection.new(representation['_links'], entry_point)
37
- @embedded = ResourceCollection.new(representation['_embedded'], entry_point)
38
- @attributes = Attributes.new(representation)
39
- @entry_point = entry_point
40
- @response = response
36
+ links = representation['_links'] || {}
37
+ @_links = LinkCollection.new(links, links['curies'], entry_point)
38
+ @_embedded = ResourceCollection.new(representation['_embedded'], entry_point)
39
+ @_attributes = Attributes.new(representation)
40
+ @_entry_point = entry_point
41
+ @_response = response
41
42
  end
42
43
 
43
44
  def inspect
44
- "#<#{self.class.name} self_link:#{self_link.inspect} attributes:#{@attributes.inspect}>"
45
+ "#<#{self.class.name} self_link:#{_self_link.inspect} attributes:#{@_attributes.inspect}>"
45
46
  end
46
47
 
47
- def success?
48
- response && response.success?
48
+ def _success?
49
+ _response && _response.success?
49
50
  end
50
51
 
51
- def status
52
- response && response.status
52
+ def _status
53
+ _response && _response.status
54
+ end
55
+
56
+ def [](name)
57
+ send(name) if respond_to?(name)
53
58
  end
54
59
 
55
60
  private
61
+
56
62
  # Internal: Returns the self Link of the Resource. Used to handle the HTTP
57
63
  # methods.
58
- def self_link
59
- @links['self']
64
+ def _self_link
65
+ @_links['self']
66
+ end
67
+
68
+ # Internal: Delegate the method to various elements of the resource.
69
+ #
70
+ # This allows `api.posts` instead of `api.links.posts.resource`
71
+ # as well as api.posts(id: 1) assuming posts is a link.
72
+ def method_missing(method, *args, &block)
73
+ if args.any? && args.first.is_a?(Hash)
74
+ _links.send(method, [], &block)._expand(*args)
75
+ else
76
+ [:_attributes, :_embedded, :_links].each do |target|
77
+ target = send(target)
78
+ return target.send(method, *args, &block) if target.respond_to?(method.to_s)
79
+ end
80
+ super
81
+ end
82
+ end
83
+
84
+ # Internal: Accessory method to allow the resource respond to
85
+ # methods that will hit method_missing.
86
+ def respond_to_missing?(method, include_private = false)
87
+ [:_attributes, :_embedded, :_links].each do |target|
88
+ return true if send(target).respond_to?(method, include_private)
89
+ end
90
+ false
60
91
  end
61
92
  end
62
93
  end
@@ -18,12 +18,13 @@ module Hyperclient
18
18
  #
19
19
  def initialize(collection, entry_point)
20
20
  @entry_point = entry_point
21
- @collection = (collection || {}).inject({}) do |hash, (name, resource)|
21
+ @collection = (collection || {}).reduce({}) do |hash, (name, resource)|
22
22
  hash.update(name => build_resource(resource))
23
23
  end
24
24
  end
25
25
 
26
26
  private
27
+
27
28
  def build_resource(representation)
28
29
  return representation.map(&method(:build_resource)) if representation.is_a?(Array)
29
30
 
@@ -1,3 +1,3 @@
1
1
  module Hyperclient
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -15,7 +15,18 @@
15
15
  "href": "/gizmos/2"
16
16
  }
17
17
  ],
18
- "null_link": null
18
+ "curies" : [
19
+ {
20
+ "name": "image",
21
+ "href": "/images/{rel}",
22
+ "templated": true
23
+ }
24
+ ],
25
+ "null_link": null,
26
+ "image:thumbnail": {
27
+ "href": "thumbnails/{version}.jpg",
28
+ "templated": true
29
+ }
19
30
  },
20
31
  "title": "Real World ASP.NET MVC3",
21
32
  "description": "In this advanced, somewhat-opinionated production you'll get your very own startup off the ground using ASP.NET MVC 3...",
@@ -4,7 +4,7 @@ require 'hyperclient/attributes'
4
4
  module Hyperclient
5
5
  describe Attributes do
6
6
  let(:representation) do
7
- JSON.parse( File.read('test/fixtures/element.json'))
7
+ JSON.parse(File.read('test/fixtures/element.json'))
8
8
  end
9
9
 
10
10
  let(:attributes) do
@@ -24,7 +24,7 @@ module Hyperclient
24
24
  attributes.permitted.must_equal true
25
25
 
26
26
  attributes.must_respond_to :title
27
- attributes.title.must_equal "Real World ASP.NET MVC3"
27
+ attributes.title.must_equal 'Real World ASP.NET MVC3'
28
28
  end
29
29
 
30
30
  # Underscores should be allowed per http://tools.ietf.org/html/draft-kelly-json-hal#appendix-B.4
@@ -4,7 +4,7 @@ require 'hyperclient/resource'
4
4
  module Hyperclient
5
5
  describe Collection do
6
6
  let(:representation) do
7
- JSON.parse( File.read('test/fixtures/element.json'))
7
+ JSON.parse(File.read('test/fixtures/element.json'))
8
8
  end
9
9
 
10
10
  let(:collection) do
@@ -28,11 +28,11 @@ module Hyperclient
28
28
  end
29
29
 
30
30
  it 'acts as enumerable' do
31
- names = collection.map do |name, value|
31
+ names = collection.map do |name, _value|
32
32
  name
33
33
  end
34
34
 
35
- names.must_equal ['_links', 'title', 'description', 'permitted', '_hidden_attribute', '_embedded']
35
+ names.must_equal %w(_links title description permitted _hidden_attribute _embedded)
36
36
  end
37
37
 
38
38
  describe '#to_hash' do
@@ -53,16 +53,16 @@ module Hyperclient
53
53
 
54
54
  describe '#fetch' do
55
55
  it 'returns the value for keys that exist' do
56
- collection.fetch('title').must_equal "Real World ASP.NET MVC3"
56
+ collection.fetch('title').must_equal 'Real World ASP.NET MVC3'
57
57
  end
58
58
 
59
59
  it 'raises an error for missing keys' do
60
- Proc.new { collection.fetch('missing key') }.must_raise KeyError
60
+ proc { collection.fetch('missing key') }.must_raise KeyError
61
61
  end
62
62
 
63
63
  describe 'with a default value' do
64
64
  it 'returns the value for keys that exist' do
65
- collection.fetch('title', 'default').must_equal "Real World ASP.NET MVC3"
65
+ collection.fetch('title', 'default').must_equal 'Real World ASP.NET MVC3'
66
66
  end
67
67
 
68
68
  it 'returns the default value for missing keys' do
@@ -73,4 +73,3 @@ module Hyperclient
73
73
 
74
74
  end
75
75
  end
76
-
@@ -0,0 +1,34 @@
1
+ require_relative '../test_helper'
2
+ require 'hyperclient/curie'
3
+ require 'hyperclient/entry_point'
4
+
5
+ module Hyperclient
6
+ describe Curie do
7
+ let(:entry_point) do
8
+ EntryPoint.new('http://api.example.org/')
9
+ end
10
+
11
+ describe 'templated?' do
12
+ it 'returns true if the curie is templated' do
13
+ curie = Curie.new({ 'name' => 'image', 'templated' => true }, entry_point)
14
+
15
+ curie.templated?.must_equal true
16
+ end
17
+
18
+ it 'returns false if the curie is not templated' do
19
+ curie = Curie.new({ 'name' => 'image' }, entry_point)
20
+
21
+ curie.templated?.must_equal false
22
+ end
23
+ end
24
+
25
+ let(:curie) do
26
+ Curie.new({ 'name' => 'image', 'href' => '/images/{?rel}', 'templated' => true }, entry_point)
27
+ end
28
+ describe '_name' do
29
+ it 'returns curie name' do
30
+ curie.name.must_equal 'image'
31
+ end
32
+ end
33
+ end
34
+ end
@@ -19,6 +19,7 @@ module Hyperclient
19
19
 
20
20
  it 'creates a Faraday connection with the default block' do
21
21
  handlers = entry_point.connection.builder.handlers
22
+ handlers.must_include FaradayMiddleware::FollowRedirects
22
23
  handlers.must_include FaradayMiddleware::EncodeJson
23
24
  handlers.must_include FaradayMiddleware::ParseJson
24
25
  handlers.must_include Faraday::Adapter::NetHttp
@@ -27,8 +28,8 @@ module Hyperclient
27
28
 
28
29
  describe 'initialize' do
29
30
  it 'sets a Link with the entry point url' do
30
- entry_point.url.must_equal 'http://my.api.org'
31
+ entry_point._url.must_equal 'http://my.api.org'
31
32
  end
32
33
  end
33
34
  end
34
- end
35
+ end
@@ -3,14 +3,14 @@ require 'hyperclient/link_collection'
3
3
 
4
4
  module Hyperclient
5
5
  describe LinkCollection do
6
- let(:entry_point) { stub('Entry point', config: {base_uri: '/'}) }
6
+ let(:entry_point) { stub('Entry point', config: { base_uri: '/' }) }
7
7
 
8
8
  let(:representation) do
9
- JSON.parse( File.read('test/fixtures/element.json'))
9
+ JSON.parse(File.read('test/fixtures/element.json'))
10
10
  end
11
11
 
12
12
  let(:links) do
13
- LinkCollection.new(representation['_links'], entry_point)
13
+ LinkCollection.new(representation['_links'], representation['_links']['curies'], entry_point)
14
14
  end
15
15
 
16
16
  it 'is a collection' do
@@ -30,7 +30,28 @@ module Hyperclient
30
30
  links['gizmos'].must_be_kind_of Array
31
31
  end
32
32
 
33
- describe "array of links" do
33
+ describe 'plain link' do
34
+ let(:plain_link) { links.self }
35
+ it 'must be correct' do
36
+ plain_link._url.must_equal '/productions/1'
37
+ end
38
+ end
39
+
40
+ describe 'templated link' do
41
+ let(:templated_link) { links.filter }
42
+ it 'must expand' do
43
+ templated_link._expand(filter: 'gizmos')._url.must_equal '/productions/1?categories=gizmos'
44
+ end
45
+ end
46
+
47
+ describe 'curied link collection' do
48
+ let(:curied_link) { links['image:thumbnail'] }
49
+ it 'must expand' do
50
+ curied_link._expand(version: 'small')._url.must_equal '/images/thumbnails/small.jpg'
51
+ end
52
+ end
53
+
54
+ describe 'array of links' do
34
55
  let(:gizmos) { links.gizmos }
35
56
 
36
57
  it 'should have 2 items' do
@@ -44,7 +65,7 @@ module Hyperclient
44
65
  end
45
66
  end
46
67
 
47
- describe "null link value" do
68
+ describe 'null link value' do
48
69
  let(:null_link) { links.null_link }
49
70
  it 'must be nil' do
50
71
  null_link.must_be_nil
@@ -10,190 +10,196 @@ module Hyperclient
10
10
 
11
11
  %w(type deprecation name profile title hreflang).each do |prop|
12
12
  describe prop do
13
- it "returns the property value" do
14
- link = Link.new({prop => 'value'}, entry_point)
15
- link.send(prop).must_equal 'value'
13
+ it 'returns the property value' do
14
+ link = Link.new('key', { prop => 'value' }, entry_point)
15
+ link.send("_#{prop}").must_equal 'value'
16
16
  end
17
17
 
18
18
  it 'returns nil if the property is not present' do
19
- link = Link.new({}, entry_point)
20
- link.send(prop).must_equal nil
19
+ link = Link.new('key', {}, entry_point)
20
+ link.send("_#{prop}").must_equal nil
21
21
  end
22
22
  end
23
23
  end
24
24
 
25
- describe 'templated?' do
25
+ describe '_templated?' do
26
26
  it 'returns true if the link is templated' do
27
- link = Link.new({'templated' => true}, entry_point)
27
+ link = Link.new('key', { 'templated' => true }, entry_point)
28
28
 
29
- link.templated?.must_equal true
29
+ link._templated?.must_equal true
30
30
  end
31
31
 
32
32
  it 'returns false if the link is not templated' do
33
- link = Link.new({}, entry_point)
33
+ link = Link.new('key', {}, entry_point)
34
34
 
35
- link.templated?.must_equal false
35
+ link._templated?.must_equal false
36
36
  end
37
37
  end
38
38
 
39
- describe 'variables' do
39
+ describe '_variables' do
40
40
  it 'returns a list of required variables' do
41
- link = Link.new({'href' => '/orders{?id,owner}', 'templated' => true}, entry_point)
41
+ link = Link.new('key', { 'href' => '/orders{?id,owner}', 'templated' => true }, entry_point)
42
42
 
43
- link.variables.must_equal ['id', 'owner']
43
+ link._variables.must_equal %w(id owner)
44
44
  end
45
45
 
46
46
  it 'returns an empty array for untemplated links' do
47
- link = Link.new({'href' => '/orders'}, entry_point)
47
+ link = Link.new('key', { 'href' => '/orders' }, entry_point)
48
48
 
49
- link.variables.must_equal []
49
+ link._variables.must_equal []
50
50
  end
51
51
  end
52
52
 
53
- describe 'expand' do
53
+ describe '_expand' do
54
54
  it 'buils a Link with the templated URI representation' do
55
- link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point)
55
+ link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point)
56
56
 
57
- Link.expects(:new).with(anything, entry_point, {id: '1'})
58
- link.expand(id: '1')
57
+ Link.expects(:new).with('key', anything, entry_point, id: '1')
58
+ link._expand(id: '1')
59
59
  end
60
60
 
61
61
  it 'raises if no uri variables are given' do
62
- link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point)
63
- lambda { link.expand }.must_raise ArgumentError
62
+ link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point)
63
+ lambda { link._expand }.must_raise ArgumentError
64
64
  end
65
65
  end
66
66
 
67
- describe 'url' do
67
+ describe '_url' do
68
68
  it 'raises when missing required uri_variables' do
69
- link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point)
69
+ link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point)
70
70
 
71
- lambda { link.url }.must_raise MissingURITemplateVariablesException
71
+ lambda { link._url }.must_raise MissingURITemplateVariablesException
72
72
  end
73
73
 
74
74
  it 'expands an uri template with variables' do
75
- link = Link.new({'href' => '/orders{?id}', 'templated' => true}, entry_point, {id: 1})
75
+ link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point, id: 1)
76
76
 
77
- link.url.must_equal '/orders?id=1'
77
+ link._url.must_equal '/orders?id=1'
78
78
  end
79
79
 
80
80
  it 'returns the link when no uri template' do
81
- link = Link.new({'href' => '/orders'}, entry_point)
82
- link.url.must_equal '/orders'
81
+ link = Link.new('key', { 'href' => '/orders' }, entry_point)
82
+ link._url.must_equal '/orders'
83
+ end
84
+
85
+ it 'aliases to_s to _url' do
86
+ link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point, id: 1)
87
+
88
+ link.to_s.must_equal '/orders?id=1'
83
89
  end
84
90
  end
85
91
 
86
- describe 'resource' do
92
+ describe '_resource' do
87
93
  it 'builds a resource with the link href representation' do
88
94
  mock_response = mock(body: {}, success?: true)
89
95
 
90
96
  Resource.expects(:new).with({}, entry_point, mock_response)
91
97
 
92
- link = Link.new({'href' => '/'}, entry_point)
93
- link.expects(:get).returns(mock_response)
98
+ link = Link.new('key', { 'href' => '/' }, entry_point)
99
+ link.expects(:_get).returns(mock_response)
94
100
 
95
- link.resource
101
+ link._resource
96
102
  end
97
103
 
98
- it "has an empty body when the response fails" do
104
+ it 'has an empty body when the response fails' do
99
105
  mock_response = mock(success?: false)
100
106
 
101
107
  Resource.expects(:new).with(nil, entry_point, mock_response)
102
108
 
103
- link = Link.new({'href' => '/'}, entry_point)
104
- link.expects(:get).returns(mock_response)
109
+ link = Link.new('key', { 'href' => '/' }, entry_point)
110
+ link.expects(:_get).returns(mock_response)
105
111
 
106
- link.resource
112
+ link._resource
107
113
  end
108
114
  end
109
115
 
110
- describe 'connection' do
116
+ describe '_connection' do
111
117
  it 'returns the entry point connection' do
112
- Link.new({}, entry_point).connection.must_equal entry_point.connection
118
+ Link.new('key', {}, entry_point)._connection.must_equal entry_point.connection
113
119
  end
114
120
  end
115
121
 
116
122
  describe 'get' do
117
123
  it 'sends a GET request with the link url' do
118
- link = Link.new({'href' => '/productions/1'}, entry_point)
124
+ link = Link.new('key', { 'href' => '/productions/1' }, entry_point)
119
125
 
120
126
  entry_point.connection.expects(:get).with('/productions/1')
121
- link.get.inspect
127
+ link._get.inspect
122
128
  end
123
129
  end
124
130
 
125
- describe 'options' do
131
+ describe '_options' do
126
132
  it 'sends a OPTIONS request with the link url' do
127
- link = Link.new({'href' => '/productions/1'}, entry_point)
133
+ link = Link.new('key', { 'href' => '/productions/1' }, entry_point)
128
134
 
129
135
  entry_point.connection.expects(:run_request).with(:options, '/productions/1', nil, nil)
130
- link.options.inspect
136
+ link._options.inspect
131
137
  end
132
138
  end
133
139
 
134
- describe 'head' do
140
+ describe '_head' do
135
141
  it 'sends a HEAD request with the link url' do
136
- link = Link.new({'href' => '/productions/1'}, entry_point)
142
+ link = Link.new('key', { 'href' => '/productions/1' }, entry_point)
137
143
 
138
144
  entry_point.connection.expects(:head).with('/productions/1')
139
- link.head.inspect
145
+ link._head.inspect
140
146
  end
141
147
  end
142
148
 
143
- describe 'delete' do
149
+ describe '_delete' do
144
150
  it 'sends a DELETE request with the link url' do
145
- link = Link.new({'href' => '/productions/1'}, entry_point)
151
+ link = Link.new('key', { 'href' => '/productions/1' }, entry_point)
146
152
 
147
153
  entry_point.connection.expects(:delete).with('/productions/1')
148
- link.delete.inspect
154
+ link._delete.inspect
149
155
  end
150
156
  end
151
157
 
152
- describe 'post' do
153
- let(:link) { Link.new({'href' => '/productions/1'}, entry_point) }
158
+ describe '_post' do
159
+ let(:link) { Link.new('key', { 'href' => '/productions/1' }, entry_point) }
154
160
 
155
161
  it 'sends a POST request with the link url and params' do
156
- entry_point.connection.expects(:post).with('/productions/1', {'foo' => 'bar'})
157
- link.post({'foo' => 'bar'}).inspect
162
+ entry_point.connection.expects(:post).with('/productions/1', 'foo' => 'bar')
163
+ link._post('foo' => 'bar').inspect
158
164
  end
159
165
 
160
166
  it 'defaults params to an empty hash' do
161
167
  entry_point.connection.expects(:post).with('/productions/1', {})
162
- link.post.inspect
168
+ link._post.inspect
163
169
  end
164
170
  end
165
171
 
166
- describe 'put' do
167
- let(:link) { Link.new({'href' => '/productions/1'}, entry_point) }
172
+ describe '_put' do
173
+ let(:link) { Link.new('key', { 'href' => '/productions/1' }, entry_point) }
168
174
 
169
175
  it 'sends a PUT request with the link url and params' do
170
- entry_point.connection.expects(:put).with('/productions/1', {'foo' => 'bar'})
171
- link.put({'foo' => 'bar'}).inspect
176
+ entry_point.connection.expects(:put).with('/productions/1', 'foo' => 'bar')
177
+ link._put('foo' => 'bar').inspect
172
178
  end
173
179
 
174
180
  it 'defaults params to an empty hash' do
175
181
  entry_point.connection.expects(:put).with('/productions/1', {})
176
- link.put.inspect
182
+ link._put.inspect
177
183
  end
178
184
  end
179
185
 
180
- describe 'patch' do
181
- let(:link) { Link.new({'href' => '/productions/1'}, entry_point) }
186
+ describe '_patch' do
187
+ let(:link) { Link.new('key', { 'href' => '/productions/1' }, entry_point) }
182
188
 
183
189
  it 'sends a PATCH request with the link url and params' do
184
- entry_point.connection.expects(:patch).with('/productions/1', {'foo' => 'bar'})
185
- link.patch({'foo' => 'bar'}).inspect
190
+ entry_point.connection.expects(:patch).with('/productions/1', 'foo' => 'bar')
191
+ link._patch('foo' => 'bar').inspect
186
192
  end
187
193
 
188
194
  it 'defaults params to an empty hash' do
189
195
  entry_point.connection.expects(:patch).with('/productions/1', {})
190
- link.patch.inspect
196
+ link._patch.inspect
191
197
  end
192
198
  end
193
199
 
194
200
  describe 'inspect' do
195
201
  it 'outputs a custom-friendly output' do
196
- link = Link.new({'href'=>'/productions/1'}, 'foo')
202
+ link = Link.new('key', { 'href' => '/productions/1' }, 'foo')
197
203
 
198
204
  link.inspect.must_include 'Link'
199
205
  link.inspect.must_include '"href"=>"/productions/1"'
@@ -201,34 +207,53 @@ module Hyperclient
201
207
  end
202
208
 
203
209
  describe 'method_missing' do
204
- before do
205
- stub_request(:get, "http://myapi.org/orders").
206
- to_return(body: '{"resource": "This is the resource"}')
207
- Resource.stubs(:new).returns(resource)
210
+ describe 'delegation' do
211
+ it 'delegates when link key matches' do
212
+ resource = Resource.new({ '_links' => { 'orders' => { 'href' => '/orders' } } }, entry_point)
213
+ stub_request(:get, 'http://api.example.org/orders').to_return(body: { '_embedded' => { 'orders' => [{ 'id' => 1 }] } })
214
+ resource.orders._embedded.orders.first.id.must_equal 1
215
+ resource.orders.first.id.must_equal 1
216
+ end
217
+
218
+ it "doesn't delegate when link key doesn't match" do
219
+ resource = Resource.new({ '_links' => { 'foos' => { 'href' => '/orders' } } }, entry_point)
220
+ stub_request(:get, 'http://api.example.org/orders').to_return(body: { '_embedded' => { 'orders' => [{ 'id' => 1 }] } })
221
+ resource.foos._embedded.orders.first.id.must_equal 1
222
+ resource.foos.first.must_equal nil
223
+ end
208
224
  end
209
225
 
210
- let(:link) { Link.new({'href' => 'http://myapi.org/orders'}, entry_point) }
211
- let(:resource) { mock('Resource') }
226
+ describe 'resource' do
227
+ before do
228
+ stub_request(:get, 'http://myapi.org/orders')
229
+ .to_return(body: '{"resource": "This is the resource"}')
230
+ Resource.stubs(:new).returns(resource)
231
+ end
212
232
 
213
- it 'delegates unkown methods to the resource' do
214
- Resource.expects(:new).returns(resource).at_least_once
215
- resource.expects(:embedded)
233
+ let(:resource) { mock('Resource') }
234
+ let(:link) { Link.new('orders', { 'href' => 'http://myapi.org/orders' }, entry_point) }
216
235
 
217
- link.embedded
218
- end
236
+ it 'delegates unkown methods to the resource' do
237
+ Resource.expects(:new).returns(resource).at_least_once
238
+ resource.expects(:embedded)
219
239
 
220
- it 'raises an error when the method does not exist in the resource' do
221
- lambda { link.this_method_does_not_exist }.must_raise(NoMethodError)
222
- end
240
+ link.embedded
241
+ end
223
242
 
224
- it 'responds to missing methods' do
225
- resource.expects(:respond_to?).with('embedded').returns(true)
226
- link.respond_to?(:embedded).must_equal true
227
- end
243
+ it 'raises an error when the method does not exist in the resource' do
244
+ lambda { link.this_method_does_not_exist }.must_raise NoMethodError
245
+ end
228
246
 
229
- it 'does not delegate to_ary to resource' do
230
- resource.expects(:to_ary).never
231
- [[link, link]].flatten.must_equal [link, link]
247
+ it 'responds to missing methods' do
248
+ resource.expects(:respond_to?).with('orders').returns(false)
249
+ resource.expects(:respond_to?).with('embedded').returns(true)
250
+ link.respond_to?(:embedded).must_equal true
251
+ end
252
+
253
+ it 'does not delegate to_ary to resource' do
254
+ resource.expects(:to_ary).never
255
+ [[link, link]].flatten.must_equal [link, link]
256
+ end
232
257
  end
233
258
  end
234
259
  end