api-resource 0.4.1 → 0.4.2

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: 6690fd3a887d05228e41156d7b4896ccba029065
4
- data.tar.gz: e44f4accee730e338320003263919368b26ae5a2
3
+ metadata.gz: 5399345c2d2f04410d0c5d101a341c6118c4f2f3
4
+ data.tar.gz: b856b7ed659a37531a2c647f6f39d482e73c303e
5
5
  SHA512:
6
- metadata.gz: fdd55298b58b2dead869554975cec7f7fd081e6f11912f7858bea6d552456320549b0a58d87127a1e4b1252ecb7304305dda9040ed305ee45b7f1176213ec1ff
7
- data.tar.gz: 54eb3a96898c86de349ba47e704063747259277cd3bf4190e26d095a5d00be0c6ab6b0febdc1c4f1e3a61f2b3340c2744974fb2c36867414cfb9463a1393c907
6
+ metadata.gz: 25e9c12ee00a9cb4094dd3b4bd1db79269b21ed9906e0a1f1a55f27fc0a3ec62034dff2db46256f9fe1430cadaeea85640cdc760e6e5ae784cc72f7e6e727ca5
7
+ data.tar.gz: 40d994e49684c47d585120f109931cb4416caec48ca156f668768ce9ac6667d1d7346df940ca6d311c9a7e4ced0930689863ab1b7f5caad90690d29c3bc86f5b
@@ -7,6 +7,10 @@ require 'active_support/core_ext/hash/indifferent_access'
7
7
  require 'active_support/core_ext/object/to_param'
8
8
 
9
9
  module ApiResource
10
+
11
+ class ResourceError < StandardError
12
+ end
13
+
10
14
  class Resource
11
15
  include ActiveModel::Model
12
16
  include ActiveModel::Serializers::JSON
@@ -28,18 +32,30 @@ module ApiResource
28
32
  self.attributes = hash if hash
29
33
  end
30
34
 
35
+ def self.resource_path
36
+ @resource_path || resource_name
37
+ end
38
+
39
+ def self.resource_path=(path)
40
+ @resource_path = path
41
+ end
42
+
31
43
  def self.resource_name
32
- self.to_s.tableize
44
+ name.to_s.tableize
45
+ end
46
+
47
+ [:resource_name, :resource_path].each do |name|
48
+ define_method(name) { self.class.public_send(name) }
33
49
  end
34
50
 
35
51
  def self.find(id)
36
- result = client(:get, {}, resource_name, id)
52
+ result = client(:get, {}, resource_path, id)
37
53
  json = JSON.parse(result)
38
54
  self.new(json['data'] || json)
39
55
  end
40
56
 
41
57
  def self.all
42
- result = client(:get, {}, resource_name)
58
+ result = client(:get, {}, resource_path)
43
59
  create_resource_collection(result)
44
60
  end
45
61
 
@@ -59,23 +75,31 @@ module ApiResource
59
75
  end
60
76
 
61
77
  def self.where(options, verb=:get)
62
- result = client(verb, options, resource_name)
78
+ result = client(verb, options, resource_path)
63
79
  create_resource_collection(result)
64
80
  end
65
81
 
66
82
  def save!
67
- if self.id
68
- self.class.client(:put, attributes, self.class.resource_name)
83
+ submit_resource(resource_path, true)
84
+ self
85
+ end
86
+
87
+ def submit!(options={})
88
+ path = options.fetch(:path, resource_path)
89
+ type = options[:type]
90
+ json = submit_resource(path, false)
91
+ meta = json['meta']
92
+ type ||= meta && meta['type']
93
+ if type
94
+ returned_type = self.class.load_class(type)
95
+ returned_type.new(json['data'] || json)
69
96
  else
70
- result = self.class.client(:post, attributes, self.class.resource_name)
71
- json = JSON.parse(result)
72
- self.attributes = json['data'] || json
97
+ self
73
98
  end
74
- self
75
99
  end
76
100
 
77
101
  def destroy!
78
- self.class.client(:delete, id, self.class.resource_name) if self.id
102
+ self.class.client(:delete, id, resource_path) if self.id
79
103
  self
80
104
  end
81
105
 
@@ -85,18 +109,43 @@ module ApiResource
85
109
 
86
110
  protected
87
111
 
112
+ def submit_resource(path, save_attributes=false)
113
+ raise ResourceError unless valid?
114
+ verb = respond_to?(:id) && id ? :put : :post
115
+ begin
116
+ result = self.class.client(verb, attributes, path)
117
+ rescue RestClient::ExceptionWithResponse => e
118
+ result = e.http_body
119
+ raise ResourceError, 'Error executing request'
120
+ ensure
121
+ json = parse_and_check_error(result)
122
+ self.attributes = json['data'] || json if verb == :post && save_attributes
123
+ end
124
+ json
125
+ end
126
+
127
+ def parse_and_check_error(result)
128
+ return nil if result.blank?
129
+ json = JSON.parse(result)
130
+ errors_hash = json['errors']
131
+ if errors_hash.is_a?(Hash)
132
+ errors_hash.each { |attr, ary| ary.each { |error| errors.add(attr, error) } }
133
+ nil
134
+ else
135
+ json
136
+ end
137
+ end
138
+
88
139
  def attributes=(hash)
89
140
  hash.each do |key, value|
90
141
  if respond_to? "#{key}="
91
142
  send("#{key}=", value)
92
143
  else
93
- # check for embedded resource
144
+ # define method for associated embedded resource(s)
94
145
  if value.is_a?(Hash)
95
- meta = value['meta']
96
- data = value['data'] || value
146
+ meta, data = value['meta'], value['data'] || value
97
147
  elsif value.is_a?(Array)
98
- meta = nil
99
- data = value
148
+ meta, data = nil, value
100
149
  else
101
150
  next
102
151
  end
@@ -124,7 +173,7 @@ module ApiResource
124
173
  end
125
174
 
126
175
  def self.with_parent_resource(parent_resource_id, parent_resource_name)
127
- result = client(:get, {}, parent_resource_name.pluralize, parent_resource_id, self.resource_name)
176
+ result = client(:get, {}, parent_resource_name.pluralize, parent_resource_id, resource_path)
128
177
  create_resource_collection(result)
129
178
  end
130
179
 
@@ -139,7 +188,7 @@ module ApiResource
139
188
  end
140
189
 
141
190
  def self.client(verb, params, *paths)
142
- raise StandardError.new('Empty base_url') if base_url.blank?
191
+ raise RuntimeError, 'Empty base_url' if base_url.blank?
143
192
  url = base_url
144
193
  url += '/' unless url.end_with?('/')
145
194
  url += paths.join('/')
@@ -1,3 +1,3 @@
1
1
  module ApiResource
2
- VERSION = '0.4.1'
2
+ VERSION = '0.4.2'
3
3
  end
@@ -30,7 +30,7 @@ RSpec.describe ApiResource::Resource do
30
30
  def req(verb, path, resource, status=200)
31
31
  stub_request(verb, "#{BlogResource.base_url}#{path}").
32
32
  with(headers: { 'Accept' => 'application/json' }).
33
- to_return(status: status, body: resource === String ? resource : resource.to_json)
33
+ to_return(status: status, body: resource.is_a?(String) || resource.blank? ? resource : resource.to_json)
34
34
  end
35
35
 
36
36
  before do
@@ -191,7 +191,7 @@ RSpec.describe ApiResource::Resource do
191
191
  expect(blog.id).to be_nil
192
192
  expected_value = { data: { id: 325, title: 'hello' } }
193
193
 
194
- stub = req(:post, '/blogs', expected_value, 201)
194
+ stub = req(:post, '/blogs', expected_value, 201)
195
195
  returned = blog.save!
196
196
  expect(returned).to eq(blog)
197
197
  check_returned_object(Blog, expected_value, blog)
@@ -199,9 +199,9 @@ RSpec.describe ApiResource::Resource do
199
199
  end
200
200
  it 'PUTs data when id is not nil' do
201
201
  blog_id = 525
202
- blog = Blog.new(id: blog_id, title: 'hello')
202
+ blog = Blog.new(id: blog_id, title: 'hello')
203
203
 
204
- stub = req(:put, '/blogs', '', 204)
204
+ stub = req(:put, '/blogs', nil, 204)
205
205
  returned = blog.save!
206
206
  expect(returned).to eq(blog)
207
207
  expect(blog.id).to eq(blog_id)
@@ -211,18 +211,60 @@ RSpec.describe ApiResource::Resource do
211
211
 
212
212
  context '#destroy!' do
213
213
  it 'DELETEs data when id is not nil' do
214
- blog = Blog.new(id: 323, title: 'hello')
215
- stub = req(:delete, '/blogs', '', 204)
214
+ blog = Blog.new(id: 323, title: 'hello')
215
+ stub = req(:delete, '/blogs', '', 204)
216
216
  returned = blog.destroy!
217
217
  expect(returned).to eq(blog)
218
218
  expect(stub).to have_been_requested
219
219
  end
220
220
  it 'not DELETE data when id is nil' do
221
- blog = Blog.new(id: nil, title: 'hello')
222
- stub = req(:delete, '/blogs', '', 204)
221
+ blog = Blog.new(id: nil, title: 'hello')
222
+ stub = req(:delete, '/blogs', '', 204)
223
223
  returned = blog.destroy!
224
224
  expect(returned).to eq(blog)
225
225
  expect(stub).to_not have_been_requested
226
226
  end
227
227
  end
228
+
229
+ context '#submit!' do
230
+
231
+ class SignUp < BlogResource
232
+ self.resource_path = 'sign_up'
233
+ attr_accessor :email, :password, :password_confirmation, :name
234
+ validates :name, presence: true
235
+ end
236
+
237
+ class User < BlogResource
238
+ attr_accessor :id, :email, :name
239
+ end
240
+
241
+ let (:sign_up) { SignUp.new(email: 'user@example.com', password: 'pass', password_confirmation: 'pass', name: 'Joe') }
242
+
243
+ before do
244
+
245
+ end
246
+ it 'uses the resource_path and uses the type in meta' do
247
+ expected_value = { data: { id: 3, email: 'user@example.com', name: 'Jane' }, meta: { type: 'user' } }
248
+ stub = req(:post, '/sign_up', expected_value, 200)
249
+ returned = sign_up.submit!
250
+ check_returned_object(User, expected_value, returned)
251
+ expect(stub).to have_been_requested
252
+ end
253
+
254
+ it 'sets the returned errors' do
255
+ expected_value = { errors: { password_confrimation: ["can't be blank"], email: ["can't be blank", 'is invalid'] } }
256
+ stub = req(:post, '/sign_up', expected_value, 422)
257
+ expect(proc { sign_up.submit! }).to raise_error(ApiResource::ResourceError)
258
+ expect(sign_up.errors.messages).to match(expected_value[:errors])
259
+ expect(stub).to have_been_requested
260
+ end
261
+
262
+ it 'no request if not valid' do
263
+ sign_up.name = nil
264
+ stub = req(:post, '/sign_up', nil, 422)
265
+ expect(proc { sign_up.submit! }).to raise_error(ApiResource::ResourceError)
266
+ expect(sign_up.errors[:name]).to be_truthy
267
+ expect(stub).to_not have_been_requested
268
+ end
269
+ end
228
270
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api-resource
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chaker Nakhli
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-20 00:00:00.000000000 Z
11
+ date: 2015-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: simple-hmac