frenetic 0.0.20.alpha.3 → 0.0.20.alpha.4
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.
- data/README.md +13 -0
- data/lib/frenetic/concerns/briefly_memoizable.rb +4 -0
- data/lib/frenetic/concerns/hal_linked.rb +6 -0
- data/lib/frenetic/concerns/member_rest_methods.rb +10 -2
- data/lib/frenetic/configuration.rb +5 -0
- data/lib/frenetic/middleware/hal_json.rb +7 -1
- data/lib/frenetic/resource.rb +4 -2
- data/lib/frenetic/resource_collection.rb +6 -2
- data/lib/frenetic/version.rb +1 -1
- data/spec/concerns/breifly_memoizable_spec.rb +10 -0
- data/spec/concerns/hal_linked_spec.rb +30 -8
- data/spec/concerns/member_rest_methods_spec.rb +28 -2
- data/spec/configuration_spec.rb +7 -0
- data/spec/fixtures/test_api_requests.rb +24 -4
- data/spec/frenetic_spec.rb +21 -3
- data/spec/resource_collection_spec.rb +7 -1
- metadata +3 -3
data/README.md
CHANGED
@@ -340,6 +340,19 @@ the schema has changed. If so, it will redefine the the getter methods available
|
|
340
340
|
on your Class. This is what Hypermedia APIs are all about, a loose coupling
|
341
341
|
between client and server.
|
342
342
|
|
343
|
+
#### Requesting Resources
|
344
|
+
|
345
|
+
Given the above `Order` example, and a supporting API, you can query the API
|
346
|
+
like so:
|
347
|
+
|
348
|
+
```ruby
|
349
|
+
> Order.find(1)
|
350
|
+
# <Order id=1 total=54.47>
|
351
|
+
|
352
|
+
> Order.all
|
353
|
+
# [<Order id=1 total=54.47>,<Order id=2 total=42.00>]
|
354
|
+
```
|
355
|
+
|
343
356
|
#### Mocking Resources
|
344
357
|
|
345
358
|
Sometimes, when you are writing tests for your API client, it is helpful to have
|
@@ -23,6 +23,10 @@ module BrieflyMemoizable
|
|
23
23
|
#
|
24
24
|
#{memoized_ivar} ||= #{original_method}(*args) # @mime_type ||= _unmemoized_mime_type(*args)
|
25
25
|
end # end
|
26
|
+
|
27
|
+
def reload_#{symbol}! # def reload_mime_type!
|
28
|
+
#{memoized_ivar} = nil # @mime_type = nil
|
29
|
+
end # end
|
26
30
|
EOS
|
27
31
|
end
|
28
32
|
end
|
@@ -28,6 +28,12 @@ class Frenetic
|
|
28
28
|
parse_link link, params
|
29
29
|
end
|
30
30
|
|
31
|
+
def collection_url
|
32
|
+
link = links[namespace.pluralize] or raise HypermediaError, %Q{No Hypermedia GET Url found for the resource "#{namespace.pluralize}"}
|
33
|
+
|
34
|
+
link['href']
|
35
|
+
end
|
36
|
+
|
31
37
|
def parse_link( link, params = {} )
|
32
38
|
if link['templated']
|
33
39
|
expand_link link, params
|
@@ -5,11 +5,19 @@ class Frenetic
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
module ClassMethods
|
8
|
-
def find(
|
9
|
-
|
8
|
+
def find( params = {} )
|
9
|
+
params = { id:params } unless params.is_a? Hash
|
10
|
+
|
11
|
+
if response = api.get( member_url(params) ) and response.success?
|
10
12
|
new response.body
|
11
13
|
end
|
12
14
|
end
|
15
|
+
|
16
|
+
def all
|
17
|
+
if response = api.get( collection_url ) and response.success?
|
18
|
+
Frenetic::ResourceCollection.new self, response.body
|
19
|
+
end
|
20
|
+
end
|
13
21
|
end
|
14
22
|
end
|
15
23
|
end
|
@@ -31,6 +31,7 @@ class Frenetic
|
|
31
31
|
default_root_cache_age: default_root_cache_age,
|
32
32
|
headers: headers,
|
33
33
|
password: password,
|
34
|
+
ssl: ssl,
|
34
35
|
url: url,
|
35
36
|
username: username
|
36
37
|
}
|
@@ -66,6 +67,10 @@ class Frenetic
|
|
66
67
|
@_cfg[:password] || @_cfg[:api_key]
|
67
68
|
end
|
68
69
|
|
70
|
+
def ssl
|
71
|
+
@_cfg[:ssl] || { verify:true }
|
72
|
+
end
|
73
|
+
|
69
74
|
def url
|
70
75
|
Addressable::URI.parse @_cfg[:url]
|
71
76
|
end
|
@@ -13,7 +13,13 @@ class Frenetic
|
|
13
13
|
raise ClientError, env[:body]['error']
|
14
14
|
end
|
15
15
|
rescue Faraday::Error::ParsingError => err
|
16
|
-
|
16
|
+
if (500...599).include? env[:status]
|
17
|
+
raise ServerError, "#{env[:status]} Error encountered"
|
18
|
+
elsif (400...499).include? env[:status]
|
19
|
+
raise ClientError, "#{env[:status]} Error encountered"
|
20
|
+
else
|
21
|
+
raise ParsingError, err.message
|
22
|
+
end
|
17
23
|
end
|
18
24
|
|
19
25
|
end
|
data/lib/frenetic/resource.rb
CHANGED
@@ -9,7 +9,7 @@ class Frenetic
|
|
9
9
|
def initialize( resource, params = {} )
|
10
10
|
@resource_class = resource
|
11
11
|
@resources = []
|
12
|
-
@params = params
|
12
|
+
@params = params || {}
|
13
13
|
|
14
14
|
extract_resources!
|
15
15
|
end
|
@@ -37,10 +37,14 @@ class Frenetic
|
|
37
37
|
private
|
38
38
|
|
39
39
|
def extract_resources!
|
40
|
-
@resources =
|
40
|
+
@resources = embedded_collection.collect do |resource|
|
41
41
|
@resource_class.new resource
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
def embedded_collection
|
46
|
+
@params.fetch('_embedded',{}).fetch(collection_key, [])
|
47
|
+
end
|
48
|
+
|
45
49
|
end
|
46
50
|
end
|
data/lib/frenetic/version.rb
CHANGED
@@ -53,6 +53,16 @@ describe BrieflyMemoizable do
|
|
53
53
|
|
54
54
|
instance.fetch
|
55
55
|
end
|
56
|
+
|
57
|
+
context 'after it has been reloaded' do
|
58
|
+
before { instance.reload_fetch! }
|
59
|
+
|
60
|
+
it 'should be called' do
|
61
|
+
instance.should_receive(:external_call).once.and_call_original
|
62
|
+
|
63
|
+
instance.fetch
|
64
|
+
end
|
65
|
+
end
|
56
66
|
end
|
57
67
|
end
|
58
68
|
end
|
@@ -38,16 +38,16 @@ describe Frenetic::HalLinked do
|
|
38
38
|
|
39
39
|
subject { MyTempResource.new(_links).member_url }
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
}
|
41
|
+
let(:_links) do
|
42
|
+
{
|
43
|
+
'_links' => {
|
44
|
+
'self' => { 'href' => '/api/self' },
|
45
|
+
'my_temp_resource' => {
|
46
|
+
'href' => '/api/my_temp_resource/{id}', 'templated' => true
|
48
47
|
}
|
49
48
|
}
|
50
|
-
|
49
|
+
}
|
50
|
+
end
|
51
51
|
|
52
52
|
context 'with a link that matches the resource name' do
|
53
53
|
it 'should return the named link' do
|
@@ -113,4 +113,26 @@ describe Frenetic::HalLinked do
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
end
|
116
|
+
|
117
|
+
describe '.collection_url' do
|
118
|
+
subject { MyTempResource.collection_url }
|
119
|
+
|
120
|
+
before { @stubs.api_description }
|
121
|
+
|
122
|
+
context 'for an unknown resource' do
|
123
|
+
before do
|
124
|
+
MyTempResource.stub(:namespace).and_return Time.now.to_i.to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should raise an error' do
|
128
|
+
expect{ subject }.to raise_error Frenetic::HypermediaError
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'with a non-templated URL' do
|
133
|
+
it 'simply return the URL' do
|
134
|
+
subject.should == '/api/my_temp_resources'
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
116
138
|
end
|
@@ -23,19 +23,45 @@ describe Frenetic::MemberRestMethods do
|
|
23
23
|
subject { MyTempResource.find 1 }
|
24
24
|
|
25
25
|
context 'for a known instance' do
|
26
|
-
before { @stubs.
|
26
|
+
before { @stubs.known_instance }
|
27
27
|
|
28
28
|
it 'should return the instance' do
|
29
29
|
expect(subject).to be_a MyTempResource
|
30
30
|
end
|
31
|
+
|
32
|
+
context 'and a Hash argument' do
|
33
|
+
subject { MyTempResource.find id:1 }
|
34
|
+
|
35
|
+
it 'should return the instance' do
|
36
|
+
expect(subject).to be_a MyTempResource
|
37
|
+
end
|
38
|
+
end
|
31
39
|
end
|
32
40
|
|
33
41
|
context 'for an unknown instance' do
|
34
|
-
before { @stubs.
|
42
|
+
before { @stubs.unknown_instance }
|
35
43
|
|
36
44
|
it 'should raise an error' do
|
37
45
|
expect{ subject }.to raise_error Frenetic::ClientError
|
38
46
|
end
|
39
47
|
end
|
40
48
|
end
|
49
|
+
|
50
|
+
describe '.all' do
|
51
|
+
before { @stubs.api_description }
|
52
|
+
|
53
|
+
subject { MyTempResource.all }
|
54
|
+
|
55
|
+
context 'for a known resource' do
|
56
|
+
before { @stubs.known_resource }
|
57
|
+
|
58
|
+
it 'should return a resource collection' do
|
59
|
+
expect(subject).to be_an_instance_of Frenetic::ResourceCollection
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should instantiate all resources in the collection' do
|
63
|
+
expect(subject.first).to be_an_instance_of MyTempResource
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
41
67
|
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -18,6 +18,7 @@ describe Frenetic::Configuration do
|
|
18
18
|
it { should include :default_root_cache_age }
|
19
19
|
it { should include :headers }
|
20
20
|
it { should include :password }
|
21
|
+
it { should include :ssl }
|
21
22
|
it { should include :url }
|
22
23
|
it { should include :username }
|
23
24
|
end
|
@@ -94,6 +95,12 @@ describe Frenetic::Configuration do
|
|
94
95
|
end
|
95
96
|
end
|
96
97
|
|
98
|
+
describe '#ssl' do
|
99
|
+
subject { instance.ssl }
|
100
|
+
|
101
|
+
it { should include verify:true }
|
102
|
+
end
|
103
|
+
|
97
104
|
describe '#url' do
|
98
105
|
let(:cfg) do
|
99
106
|
{ url:'http://example.org' }
|
@@ -25,9 +25,18 @@ class HttpStubs
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
28
|
+
def api_html_response
|
29
29
|
@rspec.stub_request( :any, 'example.com/api' )
|
30
|
-
.to_return response( body:
|
30
|
+
.to_return response( body:'Non-JSON response', status:200 )
|
31
|
+
end
|
32
|
+
|
33
|
+
def api_server_error( type = :json )
|
34
|
+
body = '500 Server Error'
|
35
|
+
|
36
|
+
body = { 'error' => body }.to_json if type == :json
|
37
|
+
|
38
|
+
@rspec.stub_request( :any, 'example.com/api' )
|
39
|
+
.to_return response( body:body, status:500 )
|
31
40
|
end
|
32
41
|
|
33
42
|
def api_client_error( type = :json )
|
@@ -44,16 +53,27 @@ class HttpStubs
|
|
44
53
|
.to_return response( body:schema, headers:{ 'Cache-Control' => 'max-age=3600, public' } )
|
45
54
|
end
|
46
55
|
|
47
|
-
def
|
56
|
+
def unknown_instance
|
48
57
|
@rspec.stub_request( :get, 'example.com/api/my_temp_resources/1' )
|
49
58
|
.to_return response( body:{ 'error' => '404 Not Found' }, status:404 )
|
50
59
|
end
|
51
60
|
|
52
|
-
def
|
61
|
+
def known_instance
|
53
62
|
@rspec.stub_request( :get, 'example.com/api/my_temp_resources/1' )
|
54
63
|
.to_return response( body:{ 'name' => 'Resource Name' } )
|
55
64
|
end
|
56
65
|
|
66
|
+
def known_resource
|
67
|
+
@rspec.stub_request( :get, 'example.com/api/my_temp_resources' )
|
68
|
+
.to_return response( body:{
|
69
|
+
'_embedded' => {
|
70
|
+
'my_temp_resources' => [
|
71
|
+
{ 'name' => 'Resource Name' }
|
72
|
+
]
|
73
|
+
}
|
74
|
+
} )
|
75
|
+
end
|
76
|
+
|
57
77
|
def schema
|
58
78
|
{
|
59
79
|
_embedded: {
|
data/spec/frenetic_spec.rb
CHANGED
@@ -78,10 +78,28 @@ describe Frenetic do
|
|
78
78
|
end
|
79
79
|
|
80
80
|
context 'JSON parsing error' do
|
81
|
-
|
81
|
+
context 'for an otherwise successful response' do
|
82
|
+
before { @stubs.api_html_response }
|
82
83
|
|
83
|
-
|
84
|
-
|
84
|
+
it 'should raise an error' do
|
85
|
+
expect{ subject }.to raise_error Frenetic::ParsingError
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'for a server error' do
|
90
|
+
before { @stubs.api_server_error :text }
|
91
|
+
|
92
|
+
it 'should raise an error' do
|
93
|
+
expect{ subject }.to raise_error Frenetic::ServerError, '500 Error encountered'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'for a client error' do
|
98
|
+
before { @stubs.api_client_error :text }
|
99
|
+
|
100
|
+
it 'should raise an error' do
|
101
|
+
expect{ subject }.to raise_error Frenetic::ClientError, '404 Error encountered'
|
102
|
+
end
|
85
103
|
end
|
86
104
|
end
|
87
105
|
end
|
@@ -67,8 +67,14 @@ describe Frenetic::ResourceCollection do
|
|
67
67
|
subject.links.should_not be_empty
|
68
68
|
end
|
69
69
|
|
70
|
+
context 'for a non-embedded resource' do
|
71
|
+
subject { described_class.new(MyTempResource) }
|
72
|
+
|
73
|
+
it { should be_empty }
|
74
|
+
end
|
75
|
+
|
70
76
|
describe '#get' do
|
71
|
-
before { @stubs.
|
77
|
+
before { @stubs.known_instance }
|
72
78
|
|
73
79
|
subject { super().get(1) }
|
74
80
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: frenetic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.20.alpha.
|
4
|
+
version: 0.0.20.alpha.4
|
5
5
|
prerelease: 7
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: faraday
|
@@ -215,7 +215,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
215
215
|
version: '0'
|
216
216
|
segments:
|
217
217
|
- 0
|
218
|
-
hash: -
|
218
|
+
hash: -3576003086095464905
|
219
219
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
220
220
|
none: false
|
221
221
|
requirements:
|