hyperclient 0.4.0 → 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.
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