api-model 0.1.1 → 0.1.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 +4 -4
- data/.rspec +1 -1
- data/Gemfile.lock +3 -3
- data/README.md +21 -0
- data/api-model.gemspec +1 -1
- data/lib/api-model.rb +2 -1
- data/lib/api_model/builder/hash.rb +11 -0
- data/lib/api_model/{cache_stategies → cache_stategy}/no_cache.rb +1 -1
- data/lib/api_model/class_methods.rb +9 -1
- data/lib/api_model/configuration.rb +1 -1
- data/lib/api_model/instance_methods.rb +56 -0
- data/spec/api-model/api_model_spec.rb +93 -0
- data/spec/api-model/configuration_spec.rb +1 -1
- data/spec/support/fixtures/posts.yml +112 -0
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 820bf511fd4b03fc1a8d9523717d6f5a880f4ed5
|
4
|
+
data.tar.gz: c412f230bde85508e50b9ccb4615b79522b8d70e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4300025629a98a9c70f6c8f81cb9b0311f5a439256bdf0a5a68d3ba0595fc67049af6e7fb17add9452b5cbb875feb221b755aa690764186ce28f00c8c79d46a9
|
7
|
+
data.tar.gz: fbf370237c20cbc647435cf72feb51d5732cc9bf4f78aaa67dfebf247d7798bba4cd72c56e527ab994e1c73e5862d9ea9e99f6fbb570220bee1753935f915cf0
|
data/.rspec
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
--color
|
2
|
-
--format
|
2
|
+
--format progress
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
api-model (0.1.
|
4
|
+
api-model (0.1.2)
|
5
5
|
activemodel
|
6
6
|
activesupport
|
7
7
|
hashie
|
@@ -52,8 +52,8 @@ GEM
|
|
52
52
|
slop (3.4.6)
|
53
53
|
thread_safe (0.1.3)
|
54
54
|
atomic
|
55
|
-
typhoeus (0.6.
|
56
|
-
ethon (~> 0.6.
|
55
|
+
typhoeus (0.6.7)
|
56
|
+
ethon (~> 0.6.2)
|
57
57
|
tzinfo (0.3.38)
|
58
58
|
vcr (2.8.0)
|
59
59
|
webmock (1.15.0)
|
data/README.md
CHANGED
@@ -253,3 +253,24 @@ These can of course be overridden by just re-defining them in the headers config
|
|
253
253
|
config.headers = { "Content-Type" => "application/soap+xml" }
|
254
254
|
end
|
255
255
|
```
|
256
|
+
|
257
|
+
### Logging requests
|
258
|
+
|
259
|
+
You can hook onto a callback on the `ApiModel::HttpRequest` class in order to perform tasks before, after or around an
|
260
|
+
API request. This is useful for logging requests. For example, if you wanted to add a custom NewRelic tracer, you could
|
261
|
+
add the following callback to make external API calls show up nicely in NewRelic:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
require 'new_relic/agent/method_tracer'
|
265
|
+
|
266
|
+
ApiModel::HttpRequest.class_eval do
|
267
|
+
include NewRelic::Agent::MethodTracer
|
268
|
+
around_run :trace_with_newrelic
|
269
|
+
|
270
|
+
def trace_with_newrelic
|
271
|
+
trace_execution_scoped(["API/#{self.method}/#{self.path}"]) do
|
272
|
+
yield
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
```
|
data/api-model.gemspec
CHANGED
@@ -2,7 +2,7 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = "api-model"
|
5
|
-
s.version = "0.1.
|
5
|
+
s.version = "0.1.2"
|
6
6
|
s.authors = ["Damien Timewell"]
|
7
7
|
s.email = ["mail@damientimewell.com"]
|
8
8
|
s.homepage = "https://github.com/iZettle/api-model"
|
data/lib/api-model.rb
CHANGED
@@ -12,8 +12,9 @@ require 'api_model/response'
|
|
12
12
|
require 'api_model/class_methods'
|
13
13
|
require 'api_model/instance_methods'
|
14
14
|
require 'api_model/configuration'
|
15
|
-
require 'api_model/
|
15
|
+
require 'api_model/cache_stategy/no_cache'
|
16
16
|
require 'api_model/response_parser/json'
|
17
|
+
require 'api_model/builder/hash'
|
17
18
|
|
18
19
|
module ApiModel
|
19
20
|
Log = Logger.new STDOUT
|
@@ -6,8 +6,16 @@ module ApiModel
|
|
6
6
|
end
|
7
7
|
|
8
8
|
def post_json(path, body=nil, options={})
|
9
|
+
call_api_with_json :post, path, body, options
|
10
|
+
end
|
11
|
+
|
12
|
+
def put_json(path, body=nil, options={})
|
13
|
+
call_api_with_json :put, path, body, options
|
14
|
+
end
|
15
|
+
|
16
|
+
def call_api_with_json(method, path, body=nil, options={})
|
9
17
|
body = body.to_json if body.is_a?(Hash)
|
10
|
-
call_api
|
18
|
+
call_api method, path, options.merge(body: body)
|
11
19
|
end
|
12
20
|
|
13
21
|
def call_api(method, path, options={})
|
@@ -1,5 +1,11 @@
|
|
1
1
|
module ApiModel
|
2
2
|
module InstanceMethods
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
extend ActiveModel::Callbacks
|
7
|
+
define_model_callbacks :save, :successful_save, :unsuccessful_save
|
8
|
+
end
|
3
9
|
|
4
10
|
# Overrides Hashie::Trash to catch errors from trying to set properties which have not been defined
|
5
11
|
# and defines it automatically
|
@@ -24,5 +30,55 @@ module ApiModel
|
|
24
30
|
end
|
25
31
|
end
|
26
32
|
|
33
|
+
# Convenience method to change attributes on an instance en-masse using a hash. This is
|
34
|
+
# useful for when an api response includes changed attributes and you want to update the current
|
35
|
+
# instance with the changes.
|
36
|
+
def update_attributes_from_hash(values={})
|
37
|
+
return unless values.present?
|
38
|
+
|
39
|
+
values.each do |key,value|
|
40
|
+
begin
|
41
|
+
public_send "#{key}=", value
|
42
|
+
rescue
|
43
|
+
Log.debug "Could not set #{key} on #{self.class.name}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Sends a request to the api to update a resource. If the response was successful, then it will
|
49
|
+
# update the instance with any changes which the API has returned. If not, it will set ActiveModel
|
50
|
+
# errors.
|
51
|
+
#
|
52
|
+
# The default request type is PUT, but you can override this by setting ++:request_method++ in the
|
53
|
+
# options hash.
|
54
|
+
#
|
55
|
+
# It also includes 3 callbacks which you can hook onto; ++save++, which is the entire method, whether
|
56
|
+
# the API request was successful or not, and ++successful_save++ and ++unsuccessful_save++ which are
|
57
|
+
# triggered on successful or unsuccessful responses.
|
58
|
+
#
|
59
|
+
# By default it uses the ++ApiModel::Builder::Hash++ builder rather than using the normal method of
|
60
|
+
# using the class, or api config builders. This is to avoid building new objects from the response,
|
61
|
+
# but can be easily overriden by passing in ++:builder++ in the options hash.
|
62
|
+
def save(path, body=nil, options={})
|
63
|
+
request_method = options.delete(:request_method) || :put
|
64
|
+
|
65
|
+
run_callbacks :save do
|
66
|
+
response = self.class.call_api_with_json request_method, path, body, options.reverse_merge(builder: ApiModel::Builder::Hash.new)
|
67
|
+
response_success = response.http_response.api_call.success?
|
68
|
+
|
69
|
+
if response_success
|
70
|
+
run_callbacks :successful_save do
|
71
|
+
update_attributes_from_hash response.response_body
|
72
|
+
end
|
73
|
+
else
|
74
|
+
run_callbacks :unsuccessful_save do
|
75
|
+
set_errors_from_hash response.response_body["errors"]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
response_success
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
27
83
|
end
|
28
84
|
end
|
@@ -21,10 +21,20 @@ describe ApiModel do
|
|
21
21
|
post_request.http_response.request_method.should eq :post
|
22
22
|
end
|
23
23
|
|
24
|
+
it 'should be possible to send a PUT request' do
|
25
|
+
put_request = VCR.use_cassette('posts') { BlogPost.put_json "/post/1" }
|
26
|
+
put_request.http_response.request_method.should eq :put
|
27
|
+
end
|
28
|
+
|
24
29
|
it 'should be possible to send a POST request with a hash as body' do
|
25
30
|
post_request = VCR.use_cassette('posts') { BlogPost.post_json "/create_with_json", name: "foobarbaz" }
|
26
31
|
post_request.http_response.api_call.request.options[:body].should eq "{\"name\":\"foobarbaz\"}"
|
27
32
|
end
|
33
|
+
|
34
|
+
it 'should be possible to send a PUT request with a hash as body' do
|
35
|
+
post_request = VCR.use_cassette('posts') { BlogPost.put_json "/post/1", name: "foobarbaz" }
|
36
|
+
post_request.http_response.api_call.request.options[:body].should eq "{\"name\":\"foobarbaz\"}"
|
37
|
+
end
|
28
38
|
end
|
29
39
|
|
30
40
|
describe "retrieving a single object" do
|
@@ -148,6 +158,89 @@ describe ApiModel do
|
|
148
158
|
end
|
149
159
|
end
|
150
160
|
|
161
|
+
describe "updating attributes from a hash" do
|
162
|
+
let(:car) { Car.new }
|
163
|
+
|
164
|
+
it 'should change an existing attribute' do
|
165
|
+
car.name = "Chevvy"
|
166
|
+
expect {
|
167
|
+
car.update_attributes_from_hash name: "Ford"
|
168
|
+
}.to change{ car.name }.from("Chevvy").to("Ford")
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'should set an attribute if unset' do
|
172
|
+
expect {
|
173
|
+
car.update_attributes_from_hash number_of_doors: 2
|
174
|
+
}.to change{ car.number_of_doors }.from(nil).to(2)
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should log if the attribute is not defined' do
|
178
|
+
ApiModel::Log.should_receive(:debug).with "Could not set age on Car"
|
179
|
+
car.update_attributes_from_hash age: 2
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "saving changes on an instance" do
|
184
|
+
before do
|
185
|
+
BlogPost.api_config { |config| config.host = "http://api-model-specs.com" }
|
186
|
+
end
|
187
|
+
|
188
|
+
let(:blog_post) { BlogPost.new }
|
189
|
+
|
190
|
+
# VCR will blow up if this was not a PUT, so no rspec expectations are needed here...
|
191
|
+
it 'should send a PUT request' do
|
192
|
+
VCR.use_cassette('posts') { blog_post.save "/post/1" }
|
193
|
+
end
|
194
|
+
|
195
|
+
# Same again here with VCR...
|
196
|
+
it 'should be possible to change the request type' do
|
197
|
+
VCR.use_cassette('posts') { blog_post.save "/post/update_with_post", nil, request_method: :post }
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'should be possible to send a JSON body in the same way a normal POST or PUT request would' do
|
201
|
+
VCR.use_cassette('posts') { blog_post.save "/post/2", name: "foobarbaz" }
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'should use #update_attributes_from_hash using the response body to update the instance' do
|
205
|
+
blog_post.should_receive(:update_attributes_from_hash).with "name" => "foobarbaz"
|
206
|
+
VCR.use_cassette('posts') { blog_post.save "/post/2", name: "foobarbaz" }
|
207
|
+
end
|
208
|
+
|
209
|
+
it 'should set errors on the instance if the response contains an errors hash' do
|
210
|
+
expect {
|
211
|
+
VCR.use_cassette('posts') { blog_post.save "/post/with_errors", name: "" }
|
212
|
+
}.to change{ blog_post.errors.size }.from(0).to(1)
|
213
|
+
blog_post.errors[:name].should eq ["Cannot be blank"]
|
214
|
+
end
|
215
|
+
|
216
|
+
describe "callbacks" do
|
217
|
+
class BlogPost
|
218
|
+
after_save :saved
|
219
|
+
after_successful_save :yay_it_saved
|
220
|
+
after_unsuccessful_save :oh_no_it_didnt_save
|
221
|
+
|
222
|
+
def saved; end
|
223
|
+
def yay_it_saved; end
|
224
|
+
def oh_no_it_didnt_save; end
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'should run a callback around the whole save method' do
|
228
|
+
blog_post.should_receive(:saved).once
|
229
|
+
VCR.use_cassette('posts') { blog_post.save "/post/1" }
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'should run a callback around the handling of a successful response' do
|
233
|
+
blog_post.should_receive(:yay_it_saved).once
|
234
|
+
VCR.use_cassette('posts') { blog_post.save "/post/1" }
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'should run a callback around the handling of a unsuccessful response' do
|
238
|
+
blog_post.should_receive(:oh_no_it_didnt_save).once
|
239
|
+
VCR.use_cassette('posts') { blog_post.save "/post/with_errors", name: "" }
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
151
244
|
describe "cache_id" do
|
152
245
|
it 'should use options and the request path to create an identifier for the cache' do
|
153
246
|
BlogPost.cache_id("/box", params: { foo: "bar" }).should eq "/boxfoobar"
|
@@ -64,7 +64,7 @@ describe ApiModel, "Configuration" do
|
|
64
64
|
|
65
65
|
describe "cache_strategy" do
|
66
66
|
it 'should default to NoCache' do
|
67
|
-
ApiModel::Base.api_model_configuration.cache_strategy.should eq ApiModel::
|
67
|
+
ApiModel::Base.api_model_configuration.cache_strategy.should eq ApiModel::CacheStrategy::NoCache
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
@@ -54,6 +54,60 @@ http_interactions:
|
|
54
54
|
http_version:
|
55
55
|
recorded_at: Thu, 28 Nov 2013 16:02:20 GMT
|
56
56
|
|
57
|
+
- request:
|
58
|
+
method: put
|
59
|
+
uri: http://api-model-specs.com/post/1
|
60
|
+
headers:
|
61
|
+
User-Agent:
|
62
|
+
- Typhoeus - https://github.com/typhoeus/typhoeus
|
63
|
+
response:
|
64
|
+
status:
|
65
|
+
code: 200
|
66
|
+
message: OK
|
67
|
+
headers:
|
68
|
+
Server:
|
69
|
+
- nginx/1.4.1
|
70
|
+
Date:
|
71
|
+
- Thu, 28 Nov 2013 16:02:56 GMT
|
72
|
+
Content-Type:
|
73
|
+
- text/plain; charset=utf-8
|
74
|
+
Content-Length:
|
75
|
+
- '248'
|
76
|
+
Connection:
|
77
|
+
- keep-alive
|
78
|
+
body:
|
79
|
+
encoding: UTF-8
|
80
|
+
string: "{\"name\":\"something_else\"}"
|
81
|
+
http_version:
|
82
|
+
recorded_at: Thu, 28 Nov 2013 16:02:20 GMT
|
83
|
+
|
84
|
+
- request:
|
85
|
+
method: post
|
86
|
+
uri: http://api-model-specs.com/post/update_with_post
|
87
|
+
headers:
|
88
|
+
User-Agent:
|
89
|
+
- Typhoeus - https://github.com/typhoeus/typhoeus
|
90
|
+
response:
|
91
|
+
status:
|
92
|
+
code: 200
|
93
|
+
message: OK
|
94
|
+
headers:
|
95
|
+
Server:
|
96
|
+
- nginx/1.4.1
|
97
|
+
Date:
|
98
|
+
- Thu, 28 Nov 2013 16:02:56 GMT
|
99
|
+
Content-Type:
|
100
|
+
- text/plain; charset=utf-8
|
101
|
+
Content-Length:
|
102
|
+
- '248'
|
103
|
+
Connection:
|
104
|
+
- keep-alive
|
105
|
+
body:
|
106
|
+
encoding: UTF-8
|
107
|
+
string: "{\"name\":\"something_else\"}"
|
108
|
+
http_version:
|
109
|
+
recorded_at: Thu, 28 Nov 2013 16:02:20 GMT
|
110
|
+
|
57
111
|
- request:
|
58
112
|
method: get
|
59
113
|
uri: http://api-model-specs.com/single_post
|
@@ -110,4 +164,62 @@ http_interactions:
|
|
110
164
|
http_version:
|
111
165
|
recorded_at: Thu, 28 Nov 2013 16:02:20 GMT
|
112
166
|
|
167
|
+
- request:
|
168
|
+
method: put
|
169
|
+
uri: http://api-model-specs.com/post/2
|
170
|
+
headers:
|
171
|
+
User-Agent:
|
172
|
+
- Typhoeus - https://github.com/typhoeus/typhoeus
|
173
|
+
body:
|
174
|
+
string: "{\"name\":\"foobarbaz\"}"
|
175
|
+
response:
|
176
|
+
status:
|
177
|
+
code: 200
|
178
|
+
message: OK
|
179
|
+
headers:
|
180
|
+
Server:
|
181
|
+
- nginx/1.4.1
|
182
|
+
Date:
|
183
|
+
- Thu, 28 Nov 2013 16:02:56 GMT
|
184
|
+
Content-Type:
|
185
|
+
- text/plain; charset=utf-8
|
186
|
+
Content-Length:
|
187
|
+
- '248'
|
188
|
+
Connection:
|
189
|
+
- keep-alive
|
190
|
+
body:
|
191
|
+
encoding: UTF-8
|
192
|
+
string: "{\"name\":\"foobarbaz\"}"
|
193
|
+
http_version:
|
194
|
+
recorded_at: Thu, 28 Nov 2013 16:02:20 GMT
|
195
|
+
|
196
|
+
- request:
|
197
|
+
method: put
|
198
|
+
uri: http://api-model-specs.com/post/with_errors
|
199
|
+
headers:
|
200
|
+
User-Agent:
|
201
|
+
- Typhoeus - https://github.com/typhoeus/typhoeus
|
202
|
+
body:
|
203
|
+
string: "{\"name\":\"\"}"
|
204
|
+
response:
|
205
|
+
status:
|
206
|
+
code: 400
|
207
|
+
message: OK
|
208
|
+
headers:
|
209
|
+
Server:
|
210
|
+
- nginx/1.4.1
|
211
|
+
Date:
|
212
|
+
- Thu, 28 Nov 2013 16:02:56 GMT
|
213
|
+
Content-Type:
|
214
|
+
- text/plain; charset=utf-8
|
215
|
+
Content-Length:
|
216
|
+
- '248'
|
217
|
+
Connection:
|
218
|
+
- keep-alive
|
219
|
+
body:
|
220
|
+
encoding: UTF-8
|
221
|
+
string: "{\"errors\":{\"name\":\"Cannot be blank\"}}"
|
222
|
+
http_version:
|
223
|
+
recorded_at: Thu, 28 Nov 2013 16:02:20 GMT
|
224
|
+
|
113
225
|
recorded_with: VCR 2.8.0
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api-model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Damien Timewell
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -139,7 +139,8 @@ files:
|
|
139
139
|
- README.md
|
140
140
|
- api-model.gemspec
|
141
141
|
- lib/api-model.rb
|
142
|
-
- lib/api_model/
|
142
|
+
- lib/api_model/builder/hash.rb
|
143
|
+
- lib/api_model/cache_stategy/no_cache.rb
|
143
144
|
- lib/api_model/class_methods.rb
|
144
145
|
- lib/api_model/configuration.rb
|
145
146
|
- lib/api_model/http_request.rb
|
@@ -182,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
182
183
|
version: '0'
|
183
184
|
requirements: []
|
184
185
|
rubyforge_project:
|
185
|
-
rubygems_version: 2.
|
186
|
+
rubygems_version: 2.2.1
|
186
187
|
signing_key:
|
187
188
|
specification_version: 4
|
188
189
|
summary: A simple way of interacting with rest APIs
|