bootic_client 0.0.2 → 0.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c3577184c13b3c030b8e7bce4bd7e560856564de
4
- data.tar.gz: 06ceb2c371d1f60bdb4f246821397789401aa4d9
3
+ metadata.gz: 727d4868928f07359322ccc49dfed3d20edfae0a
4
+ data.tar.gz: 7626595ffbef58af0b052b6a74c3dc220b3e32ca
5
5
  SHA512:
6
- metadata.gz: 5de054c0e2b96d5782e857f962991d27118552d23bd973d581a890d3074a46edb5869c36ff179a142f7b261dadb9d905ab7dae38d7b729d3b600b4bca8bbbff6
7
- data.tar.gz: 43a6af6c0fef9c9bb91481b29a94fe788578778ff4a3d76942a56c7ce32992b3d5d7a7a5c05bc63fe0813c6b7837b17871da49f4764ebb55f069eef86663e09d
6
+ metadata.gz: 56d7d5c1c16e19f61f5d9e59de8b494f3ba20f8510c7eaba5d21a1d2aeab1a97eaf125c80c5923b524dd422c6f75904ddb5f20fd3c98f4d52765dce18e2e1071
7
+ data.tar.gz: 8fb91e3fcfbed8a09bf3903e3952f9078554b381d657eec9d92b08b1e955741a40c5e3c46b2f67dada5f3d0cac45feb1a337d2536c25a762606835a27af2ef94
data/README.md CHANGED
@@ -88,6 +88,48 @@ client = BooticClient.client(:client_credentials, scope: 'admin', access_token:
88
88
  end
89
89
  ```
90
90
 
91
+ ## Non GET links
92
+
93
+ Most resource links lead to `GET` resources, but some will expect `POST`, `PUT`, `DELETE` or others.
94
+
95
+ The Bootic API encodes this information in its link metadata so the client will do the right thing. The following example creates a new product on your first shop:
96
+
97
+ ```ruby
98
+ bootic = BooticClient.client(:client_credentials)
99
+
100
+ root = bootic.root
101
+
102
+ shop = root.shops.first
103
+
104
+ if shop.can?(:create_product)
105
+ product = shop.create_product(
106
+ title: 'A shiny new product',
107
+ price: 122332,
108
+ status: "visible",
109
+ collecton_names: ["Featured products", "Homepage"],
110
+ variants: [
111
+ {
112
+ title: 'First variant',
113
+ sku: 'F23332-X',
114
+ available_if_no_stock: 1,
115
+ stock: 12
116
+ }
117
+ ]
118
+ )
119
+
120
+ puts product.rels[:web].href # => 'http://acme.bootic.net/products/a-shiny-new-product'
121
+ end
122
+ ```
123
+
124
+ ## Relation docs
125
+
126
+ All resource link relations include a "docs" URL so you can learn more about that particular resource.
127
+
128
+ ```ruby
129
+ shop = root.shops.first
130
+ puts shop.rels[:create_product] # => 'https://developers.bootic.net/rels/create_product'
131
+ ```
132
+
91
133
  ## Cache storage
92
134
 
93
135
  `BooticClient` honours HTTP caching headers included in API responses (such as `ETag` and `Last-Modified`).
@@ -9,6 +9,7 @@ module BooticClient
9
9
  class Client
10
10
 
11
11
  USER_AGENT = "[BooticClient v#{VERSION}] Ruby-#{RUBY_VERSION} - #{RUBY_PLATFORM}".freeze
12
+ JSON_MIME = 'application/json'.freeze
12
13
 
13
14
  attr_reader :options, :api_root
14
15
 
@@ -28,19 +29,30 @@ module BooticClient
28
29
  wrapper_class.new get(href, query).body, self
29
30
  end
30
31
 
31
- def get(href, query = {})
32
- validate_request!
32
+ def post_and_wrap(href, wrapper_class, payload = {})
33
+ wrapper_class.new post(href, payload).body, self
34
+ end
33
35
 
34
- resp = conn.get do |req|
35
- req.url href
36
- req.params.update(query)
37
- req.headers['Authorization'] = "Bearer #{options[:access_token]}"
38
- req.headers['User-Agent'] = USER_AGENT
36
+ def get(href, query = {})
37
+ validated! do
38
+ conn.get do |req|
39
+ req.url href
40
+ req.headers.update request_headers
41
+ req.params.update(query)
42
+ end
39
43
  end
44
+ end
40
45
 
41
- raise_if_invalid! resp
42
-
43
- resp
46
+ def post(href, payload = {})
47
+ validated! do
48
+ conn.post do |req|
49
+ req.url href
50
+ req.headers.update request_headers
51
+ req.headers['Accept'] = JSON_MIME
52
+ req.headers['Content-Type'] = JSON_MIME
53
+ req.body = JSON.dump(payload)
54
+ end
55
+ end
44
56
  end
45
57
 
46
58
  protected
@@ -58,6 +70,20 @@ module BooticClient
58
70
  end
59
71
  end
60
72
 
73
+ def request_headers
74
+ {
75
+ 'Authorization' => "Bearer #{options[:access_token]}",
76
+ 'User-Agent' => USER_AGENT
77
+ }
78
+ end
79
+
80
+ def validated!(&block)
81
+ validate_request!
82
+ resp = yield
83
+ raise_if_invalid! resp
84
+ resp
85
+ end
86
+
61
87
  def validate_request!
62
88
  raise NoAccessTokenError, "Missing access token" unless options[:access_token]
63
89
  end
@@ -23,6 +23,10 @@ module BooticClient
23
23
  has_property?(prop_name) || has_entity?(prop_name) || has_rel?(prop_name)
24
24
  end
25
25
 
26
+ def can?(rel_name)
27
+ has_rel? rel_name
28
+ end
29
+
26
30
  def inspect
27
31
  %(#<#{self.class.name} props: [#{properties.keys.join(', ')}] rels: [#{rels.keys.join(', ')}] entities: [#{entities.keys.join(', ')}]>)
28
32
  end
@@ -55,7 +59,7 @@ module BooticClient
55
59
  elsif has_entity?(name)
56
60
  entities[name]
57
61
  elsif has_rel?(name)
58
- rels[name].get(*args)
62
+ rels[name].run(*args)
59
63
  else
60
64
  super
61
65
  end
@@ -5,6 +5,8 @@ module BooticClient
5
5
 
6
6
  class Relation
7
7
 
8
+ GET = 'get'.freeze
9
+
8
10
  def initialize(attrs, client, wrapper_class = Entity)
9
11
  @attrs, @client, @wrapper_class = attrs, client, wrapper_class
10
12
  end
@@ -37,6 +39,14 @@ module BooticClient
37
39
  attrs['docs']
38
40
  end
39
41
 
42
+ def transport_method
43
+ @transport_method ||= attrs['method'] || GET
44
+ end
45
+
46
+ def run(opts = {})
47
+ self.send(transport_method, opts)
48
+ end
49
+
40
50
  def get(opts = {})
41
51
  if templated?
42
52
  client.get_and_wrap uri.expand(opts), wrapper_class
@@ -45,6 +55,10 @@ module BooticClient
45
55
  end
46
56
  end
47
57
 
58
+ def post(opts = {})
59
+ client.post_and_wrap href, wrapper_class, opts
60
+ end
61
+
48
62
  def self.expand(href, opts = {})
49
63
  URITemplate.new(href).expand(opts)
50
64
  end
@@ -1,3 +1,3 @@
1
1
  module BooticClient
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/spec/client_spec.rb CHANGED
@@ -135,5 +135,20 @@ describe BooticClient::Client do
135
135
  end
136
136
  end
137
137
 
138
+ describe '#post_and_wrap' do
139
+ before do
140
+ stub_request(:post, root_url)
141
+ .with(body: JSON.dump({foo: 'bar'}), headers: {'Accept' => 'application/json', 'Content-Type' => 'application/json'})
142
+ .to_return(status: 200, body: JSON.dump(root_data), headers: response_headers)
143
+ end
144
+
145
+ it 'wraps JSON response in entity' do
146
+ wrapper = double('Wrapper Class')
147
+ entity = double('Entity')
148
+ expect(wrapper).to receive(:new).with(root_data, client).and_return entity
149
+ expect(client.post_and_wrap(root_url, wrapper, foo: 'bar')).to eql(entity)
150
+ end
151
+ end
152
+
138
153
  end
139
154
  end
data/spec/entity_spec.rb CHANGED
@@ -111,6 +111,11 @@ describe BooticClient::Entity do
111
111
  expect(entity.has?(:next)).to eql(true)
112
112
  end
113
113
 
114
+ it 'responds to #can? for link relations' do
115
+ expect(entity.can?(:next)).to eql(true)
116
+ expect(entity.can?(:foo)).to eql(false)
117
+ end
118
+
114
119
  it 'builds relation objects' do
115
120
  expect(entity.rels[:next]).to be_kind_of(BooticClient::Relation)
116
121
  expect(entity.rels[:next].href).to eql('/foo?page=2')
@@ -13,42 +13,53 @@ describe BooticClient::Relation do
13
13
  end
14
14
  end
15
15
 
16
- describe '#get' do
16
+ describe '#run' do
17
17
  let(:entity) { BooticClient::Entity.new({'title' => 'Foobar'}, client) }
18
18
 
19
- it 'fetches data and returns entity' do
20
- client.stub(:get_and_wrap).with('/foo/bars', BooticClient::Entity, {}).and_return entity
21
- expect(relation.get).to eql(entity)
22
- end
19
+ describe 'running GET by default' do
20
+ it 'fetches data and returns entity' do
21
+ client.stub(:get_and_wrap).with('/foo/bars', BooticClient::Entity, {}).and_return entity
22
+ expect(relation.run).to eql(entity)
23
+ end
23
24
 
24
- context 'without URI templates' do
25
- let(:relation) { BooticClient::Relation.new({'href' => '/foos/bar', 'type' => 'application/json', 'title' => 'A relation'}, client) }
25
+ context 'without URI templates' do
26
+ let(:relation) { BooticClient::Relation.new({'href' => '/foos/bar', 'type' => 'application/json', 'title' => 'A relation'}, client) }
26
27
 
27
- it 'is not templated' do
28
- expect(relation.templated?).to eql(false)
29
- end
28
+ it 'is not templated' do
29
+ expect(relation.templated?).to eql(false)
30
+ end
30
31
 
31
- it 'passes query string to client' do
32
- expect(client).to receive(:get_and_wrap).with('/foos/bar', BooticClient::Entity, id: 2, q: 'test', page: 2).and_return entity
33
- expect(relation.get(id: 2, q: 'test', page: 2)).to eql(entity)
32
+ it 'passes query string to client' do
33
+ expect(client).to receive(:get_and_wrap).with('/foos/bar', BooticClient::Entity, id: 2, q: 'test', page: 2).and_return entity
34
+ expect(relation.run(id: 2, q: 'test', page: 2)).to eql(entity)
35
+ end
34
36
  end
35
- end
36
37
 
37
- context 'with URI templates' do
38
- let(:relation) { BooticClient::Relation.new({'href' => '/foos/{id}{?q,page}', 'type' => 'application/json', 'title' => 'A relation', 'templated' => true}, client) }
38
+ context 'with URI templates' do
39
+ let(:relation) { BooticClient::Relation.new({'href' => '/foos/{id}{?q,page}', 'type' => 'application/json', 'title' => 'A relation', 'templated' => true}, client) }
39
40
 
40
- it 'is templated' do
41
- expect(relation.templated?).to eql(true)
42
- end
41
+ it 'is templated' do
42
+ expect(relation.templated?).to eql(true)
43
+ end
44
+
45
+ it 'works with defaults' do
46
+ expect(client).to receive(:get_and_wrap).with('/foos/', BooticClient::Entity).and_return entity
47
+ expect(relation.run).to eql(entity)
48
+ end
43
49
 
44
- it 'works with defaults' do
45
- expect(client).to receive(:get_and_wrap).with('/foos/', BooticClient::Entity).and_return entity
46
- expect(relation.get).to eql(entity)
50
+ it 'interpolates tokens' do
51
+ expect(client).to receive(:get_and_wrap).with('/foos/2?q=test&page=2', BooticClient::Entity).and_return entity
52
+ expect(relation.run(id: 2, q: 'test', page: 2)).to eql(entity)
53
+ end
47
54
  end
55
+ end
56
+
57
+ describe 'POST' do
58
+ let(:relation) { BooticClient::Relation.new({'href' => '/foo/bars', 'type' => 'application/json', 'name' => 'self', 'method' => 'post'}, client) }
48
59
 
49
- it 'interpolates tokens' do
50
- expect(client).to receive(:get_and_wrap).with('/foos/2?q=test&page=2', BooticClient::Entity).and_return entity
51
- expect(relation.get(id: 2, q: 'test', page: 2)).to eql(entity)
60
+ it 'POSTS data and returns resulting entity' do
61
+ client.stub(:post_and_wrap).with('/foo/bars', BooticClient::Entity, {}).and_return entity
62
+ expect(relation.run).to eql(entity)
52
63
  end
53
64
  end
54
65
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootic_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ismael Celis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-17 00:00:00.000000000 Z
11
+ date: 2014-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday