frenetic 0.0.20.alpha.3 → 0.0.20.alpha.4
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|