api-model 0.0.3 → 0.0.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +234 -1
- data/api-model.gemspec +2 -3
- data/lib/api-model.rb +3 -0
- data/lib/api_model/cache_stategies/no_cache.rb +14 -0
- data/lib/api_model/configuration.rb +15 -1
- data/lib/api_model/http_request.rb +5 -1
- data/lib/api_model/response.rb +12 -11
- data/lib/api_model/response_parser/json.rb +14 -0
- data/lib/api_model/rest_methods.rb +18 -4
- data/spec/api-model/api_model_spec.rb +6 -0
- data/spec/api-model/configuration_spec.rb +48 -0
- data/spec/api-model/json_response_parser_spec.rb +17 -0
- data/spec/api-model/response_spec.rb +5 -17
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd7712e1eb0b5faac320b38518575bfbdeaf61b8
|
4
|
+
data.tar.gz: 2ea94f183ad7de875863ff989327dc414d2dc54a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f1e74e2574025ccf61e563ca0631e855211ff6953cb36a2d4986ef8c301c8477cda1913421c66d103dc225db2f10653693feec8706cbbd989dae3a60e351f585
|
7
|
+
data.tar.gz: 68171597b2cb8448a10237485904d3353012196cc911d6c1cf09648e0e0e1d7f84bb5957e29ce51b4648fd995cd095bf9467c70ff26cae40ff1d385be17c4eae
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
api-model (0.0.
|
4
|
+
api-model (0.0.4)
|
5
5
|
activemodel
|
6
6
|
activesupport
|
7
7
|
hashie
|
@@ -29,7 +29,7 @@ GEM
|
|
29
29
|
ethon (0.6.1)
|
30
30
|
ffi (>= 1.3.0)
|
31
31
|
mime-types (~> 1.18)
|
32
|
-
ffi (1.9.
|
32
|
+
ffi (1.9.3)
|
33
33
|
hashie (2.0.5)
|
34
34
|
i18n (0.6.9)
|
35
35
|
method_source (0.8.2)
|
data/README.md
CHANGED
@@ -1,4 +1,237 @@
|
|
1
1
|
[](https://codeclimate.com/github/iZettle/api-model)
|
2
2
|
[](https://travis-ci.org/iZettle/api-model)
|
3
3
|
|
4
|
-
|
4
|
+
API Model
|
5
|
+
=========
|
6
|
+
|
7
|
+
API model is a simple wrapper for interacting with external APIs. It tries to make
|
8
|
+
it very simple and easy to make API calls and map the responses into objects.
|
9
|
+
|
10
|
+
A really simple example
|
11
|
+
-----------------------
|
12
|
+
|
13
|
+
To turn any class into an API model, it must inherit ApiModel::Base. If you want to
|
14
|
+
make attributes which will get automatically set from api responses, you can define them
|
15
|
+
as properties..
|
16
|
+
|
17
|
+
``` ruby
|
18
|
+
class MyModel < ApiModel::Base
|
19
|
+
property :name
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
Then, let's say the API endpoint /foo returned JSON which looks like `{ "name": "Bar" }`...
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
example = MyModel.get_json "/foo"
|
27
|
+
example.name #=> "Bar"
|
28
|
+
```
|
29
|
+
|
30
|
+
Request types and params
|
31
|
+
------------------------
|
32
|
+
|
33
|
+
There's a couple of convenience methods to make it simpler to send GET and POST requests,
|
34
|
+
or you can send other request types:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
# Params will be sent as url params, and options is used for other things which
|
38
|
+
# can control ApiModel (such as custom builders)
|
39
|
+
get_json url, params, options
|
40
|
+
|
41
|
+
# The request body will be turned into json if it is a hash, otherwise it
|
42
|
+
# should be a string. Options are handled the same as get.
|
43
|
+
post_json url, request_body, options
|
44
|
+
|
45
|
+
# Works the same as the ones above, except if you want to pass params or body,
|
46
|
+
# they need to be within the options hash.
|
47
|
+
call_api :put, url, options
|
48
|
+
```
|
49
|
+
|
50
|
+
Model properties
|
51
|
+
----------------
|
52
|
+
|
53
|
+
The properties which you can define on models are extended from the [Hashie](https://github.com/intridea/hashie#trash)
|
54
|
+
gem. You can use them to define simple attributes, but also for converting attributes from one name to another, or for
|
55
|
+
transforming the values as they are set. This is useful for dealing with APIs which use a different naming scheme
|
56
|
+
than you are using, or if you need to modify values as they come in.
|
57
|
+
|
58
|
+
### Translation
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
class MyModel < ApiModel::Base
|
62
|
+
property :full_name, from: :fullName
|
63
|
+
end
|
64
|
+
|
65
|
+
MyModel.new(fullName: "Hello").full_name # => "Hello"
|
66
|
+
```
|
67
|
+
|
68
|
+
### Transformation
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
class MyModel < ApiModel::Base
|
72
|
+
property :created_at, from: :timestamp, with: lambda { |t| Time.at(t) }
|
73
|
+
end
|
74
|
+
|
75
|
+
MyModel.new(timestamp: 1387550991).created_at # => 2013-12-20 15:49:51 +0100
|
76
|
+
```
|
77
|
+
|
78
|
+
### Defaults
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
class MyModel < ApiModel::Base
|
82
|
+
property :name, default: "FooBar"
|
83
|
+
end
|
84
|
+
|
85
|
+
MyModel.new.name # => "FooBar"
|
86
|
+
```
|
87
|
+
|
88
|
+
For more information, check out the [Hashie::Trash docs](https://github.com/intridea/hashie#trash).
|
89
|
+
|
90
|
+
Building objects from responses
|
91
|
+
-------------------------------
|
92
|
+
|
93
|
+
If an API response begins with a hash, it is assumed that it represents a single object and so will be used
|
94
|
+
to try and build a single object. Likewise, if it is an array, it is assumed to be a collection of objects. For example:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
# GET /foo returns { "name": "Foo" }
|
98
|
+
MyModel.get_json("/foo") # => #<MyModel:0x007 @name="Foo">
|
99
|
+
|
100
|
+
# GET /bar returns [{ "name": "Foo" }, { "name": "Bar" }]
|
101
|
+
MyModel.get_json("/bar") # => [#<MyModel:0x007 @name="Foo">, #<MyModel:0x007 @name="Bar">]
|
102
|
+
```
|
103
|
+
|
104
|
+
You can override the default builder either on a per-call basis using the `:builder` option. The class which you
|
105
|
+
use as a builder should respond to `#build`, with the instance hash as an argument:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
class MyCustomBuilder
|
109
|
+
def build(params)
|
110
|
+
# build something with params...
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
MyModel.get_json "/foo", { some_param: "bar" }, builder: MyCustomBuilder.new
|
115
|
+
```
|
116
|
+
|
117
|
+
Configuring API Model
|
118
|
+
---------------------
|
119
|
+
|
120
|
+
You can configure API model in a number of places; globally using `ApiModel::Base.api_config`, per-model
|
121
|
+
using `MyModel.api_config`, and per-api call by passing in options in the options hash (although some
|
122
|
+
configuration options may not be available on the per-api call technique).
|
123
|
+
|
124
|
+
### API Host
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
ApiModel::Base.api_config do |config|
|
128
|
+
config.api_host = "http:://someserver.com"
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
This will set the root of all api calls so that you can just use paths in your models instead of having
|
133
|
+
to refer to the full url all the time.
|
134
|
+
|
135
|
+
### JSON root
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
ApiModel::Base.api_config do |config|
|
139
|
+
config.json_root = "data.posts"
|
140
|
+
end
|
141
|
+
```
|
142
|
+
|
143
|
+
If the API response which you receive is deeply nested and you want to cut out some levels of nesting, you
|
144
|
+
can use `json_root` to set which key objects should be built from.
|
145
|
+
|
146
|
+
You can dig down multiple levels by separating keys with a period. With the example above, say the server
|
147
|
+
was returning JSON which looked like `{"data":{"posts":{"name":"Foo"}}}`, it would behave as if the
|
148
|
+
response was really just `{"name":"Foo"}`.
|
149
|
+
|
150
|
+
### Builder
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
ApiModel::Base.api_config do |config|
|
154
|
+
config.builder = MyCustomBuilder.new
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
Sets a custom builder for all API calls. See [building objects from responses](#building-objects-from-responses)
|
159
|
+
for more details on how custom builders should behave.
|
160
|
+
|
161
|
+
### Parser
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
ApiModel::Base.api_config do |config|
|
165
|
+
config.parser = MyCustomParser.new
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
ApiModel is built on the assumption that most modern APIs are JSON-based, but if you need to interact with
|
170
|
+
an API which returns something other than JSON, you can set custom parsers to deal with objectifying responses
|
171
|
+
before they are sent to builder classes. The parser should work in the same way as a custom builder, except it needs
|
172
|
+
to respond to `#parse`, with the raw response body as an argument.
|
173
|
+
|
174
|
+
### Raise on not found or unauthenticated
|
175
|
+
|
176
|
+
```ruby
|
177
|
+
ApiModel::Base.api_config do |config|
|
178
|
+
config.raise_on_not_found = true
|
179
|
+
config.raise_on_unauthenticated = true
|
180
|
+
end
|
181
|
+
```
|
182
|
+
|
183
|
+
This will cause any API requests which return a 404 status to raise an ApiModel::NotFoundError exception, and requests
|
184
|
+
which return a 401 to raise an ApiModel::UnauthenticatedError exception. Both default to `false`.
|
185
|
+
|
186
|
+
### Cache strategy & settings
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
ApiModel::Base.api_config do |config|
|
190
|
+
config.cache_strategy = MyCustomCacheStrategy
|
191
|
+
config.cache_settings = { any_custom_settings: 123 }
|
192
|
+
end
|
193
|
+
```
|
194
|
+
|
195
|
+
Currently, ApiModel has no built-in cache strategy, but provides the interface for you to insert your own caching
|
196
|
+
strategy. On each API call, the cache strategy class will be initialized with two arguments; the cache id, which
|
197
|
+
is generated from the path and params, and the `cache_settings` which you can define on the config object as
|
198
|
+
shown above. It will then call `#cache` with the ApiModel response block. So your custom cache class needs to look
|
199
|
+
something like this:
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
class MyCustomCacheStrategy
|
203
|
+
attr_accessor :id, :options
|
204
|
+
|
205
|
+
def initialize(id, options)
|
206
|
+
@id = id
|
207
|
+
@options = options
|
208
|
+
end
|
209
|
+
|
210
|
+
def cache(&block)
|
211
|
+
# here you can check whether you want to actually call the api by running
|
212
|
+
# block.call, or want to find and return your cached response.
|
213
|
+
end
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
### Headers
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
ApiModel::Base.api_config do |config|
|
221
|
+
config.headers = { some_custom_header: "foo" }
|
222
|
+
end
|
223
|
+
```
|
224
|
+
|
225
|
+
Adds custom headers to the requests. By default, ApiModel will add these headers:
|
226
|
+
|
227
|
+
```ruby
|
228
|
+
{ "Content-Type" => "application/json; charset=utf-8", "Accept" => "application/json" }
|
229
|
+
```
|
230
|
+
|
231
|
+
These can of course be overridden by just re-defining them in the headers config:
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
ApiModel::Base.api_config do |config|
|
235
|
+
config.headers = { "Content-Type" => "application/soap+xml" }
|
236
|
+
end
|
237
|
+
```
|
data/api-model.gemspec
CHANGED
@@ -2,14 +2,13 @@ $:.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.0.
|
5
|
+
s.version = "0.0.4"
|
6
6
|
s.authors = ["Damien Timewell"]
|
7
7
|
s.email = ["mail@damientimewell.com"]
|
8
8
|
s.homepage = "https://github.com/iZettle/api-model"
|
9
9
|
s.summary = "A simple way of interacting with rest APIs"
|
10
|
-
s.description = "
|
10
|
+
s.description = "API model is a simple wrapper for interacting with external APIs. It tries to make it very simple and easy to make API calls and map the responses into objects."
|
11
11
|
|
12
|
-
# s.add_dependency 'redis'
|
13
12
|
s.add_dependency 'activesupport'
|
14
13
|
s.add_dependency 'activemodel'
|
15
14
|
s.add_dependency 'typhoeus'
|
data/lib/api-model.rb
CHANGED
@@ -3,12 +3,15 @@ require 'active_support'
|
|
3
3
|
require 'active_support/core_ext'
|
4
4
|
require 'logger'
|
5
5
|
require 'hashie'
|
6
|
+
require 'ostruct'
|
6
7
|
|
7
8
|
require 'api_model/initializer'
|
8
9
|
require 'api_model/http_request'
|
9
10
|
require 'api_model/response'
|
10
11
|
require 'api_model/rest_methods'
|
11
12
|
require 'api_model/configuration'
|
13
|
+
require 'api_model/cache_stategies/no_cache'
|
14
|
+
require 'api_model/response_parser/json'
|
12
15
|
|
13
16
|
module ApiModel
|
14
17
|
Log = Logger.new STDOUT
|
@@ -2,7 +2,8 @@ module ApiModel
|
|
2
2
|
class Configuration
|
3
3
|
include Initializer
|
4
4
|
|
5
|
-
attr_accessor :host, :json_root, :headers, :raise_on_unauthenticated, :
|
5
|
+
attr_accessor :host, :json_root, :headers, :raise_on_unauthenticated, :cache_settings,
|
6
|
+
:raise_on_not_found, :cache_strategy, :parser, :builder
|
6
7
|
|
7
8
|
def self.from_inherited_config(config)
|
8
9
|
new config.instance_values.reject {|k,v| v.blank? }
|
@@ -12,6 +13,19 @@ module ApiModel
|
|
12
13
|
@headers ||= {}
|
13
14
|
@headers.reverse_merge "Content-Type" => "application/json; charset=utf-8", "Accept" => "application/json"
|
14
15
|
end
|
16
|
+
|
17
|
+
def cache_strategy
|
18
|
+
@cache_strategy ||= ApiModel::CacheStrategies::NoCache
|
19
|
+
end
|
20
|
+
|
21
|
+
def parser
|
22
|
+
@parser ||= ApiModel::ResponseParser::Json.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def cache_settings
|
26
|
+
@cache_settings ||= {}
|
27
|
+
@cache_settings.reverse_merge duration: 30.seconds, timeout: 2.seconds
|
28
|
+
end
|
15
29
|
end
|
16
30
|
|
17
31
|
module ConfigurationMethods
|
@@ -2,12 +2,16 @@ module ApiModel
|
|
2
2
|
class HttpRequest
|
3
3
|
include ApiModel::Initializer
|
4
4
|
|
5
|
-
attr_accessor :path, :method, :options, :api_call, :builder, :config
|
5
|
+
attr_accessor :path, :method, :options, :api_call, :builder, :config, :cache_id
|
6
6
|
|
7
7
|
after_initialize :set_default_options
|
8
8
|
|
9
9
|
define_model_callbacks :run
|
10
10
|
|
11
|
+
def config
|
12
|
+
@config ||= Configuration.new
|
13
|
+
end
|
14
|
+
|
11
15
|
def run
|
12
16
|
run_callbacks :run do
|
13
17
|
self.api_call = Typhoeus.send method, full_path, options
|
data/lib/api_model/response.rb
CHANGED
@@ -14,14 +14,18 @@ module ApiModel
|
|
14
14
|
@_config = config || Configuration.new
|
15
15
|
end
|
16
16
|
|
17
|
+
def metadata
|
18
|
+
@metadata ||= OpenStruct.new
|
19
|
+
end
|
20
|
+
|
17
21
|
def build_objects
|
18
22
|
raise UnauthenticatedError if @_config.raise_on_unauthenticated && http_response.api_call.response_code == 401
|
19
23
|
raise NotFoundError if @_config.raise_on_not_found && http_response.api_call.response_code == 404
|
20
|
-
return if
|
24
|
+
return if response_body.nil?
|
21
25
|
|
22
26
|
if response_build_hash.is_a? Array
|
23
27
|
self.objects = response_build_hash.collect{ |hash| build http_response.builder, hash }
|
24
|
-
|
28
|
+
else
|
25
29
|
self.objects = self.build http_response.builder, response_build_hash
|
26
30
|
end
|
27
31
|
|
@@ -36,11 +40,8 @@ module ApiModel
|
|
36
40
|
end
|
37
41
|
end
|
38
42
|
|
39
|
-
def
|
40
|
-
@
|
41
|
-
rescue JSON::ParserError
|
42
|
-
Log.info "Could not parse JSON response: #{http_response.api_call.body}"
|
43
|
-
return nil
|
43
|
+
def response_body
|
44
|
+
@response_body ||= @_config.parser.parse http_response.api_call.body
|
44
45
|
end
|
45
46
|
|
46
47
|
# Define common methods which should never be called on this abstract class, and should always be
|
@@ -59,7 +60,7 @@ module ApiModel
|
|
59
60
|
|
60
61
|
private
|
61
62
|
|
62
|
-
# If the model config defines a json root, use it on the
|
63
|
+
# If the model config defines a json root, use it on the response_body
|
63
64
|
# to dig down in to the hash.
|
64
65
|
#
|
65
66
|
# The root for a deeply nested hash will come in as a string with key names split
|
@@ -67,14 +68,14 @@ module ApiModel
|
|
67
68
|
def response_build_hash
|
68
69
|
if @_config.json_root.present?
|
69
70
|
begin
|
70
|
-
@_config.json_root.split(".").inject(
|
71
|
+
@_config.json_root.split(".").inject(response_body) do |hash,key|
|
71
72
|
hash.fetch(key)
|
72
73
|
end
|
73
74
|
rescue
|
74
|
-
raise ResponseBuilderError, "Could not find key #{@_config.json_root} in:\n#{
|
75
|
+
raise ResponseBuilderError, "Could not find key #{@_config.json_root} in:\n#{response_body}"
|
75
76
|
end
|
76
77
|
else
|
77
|
-
|
78
|
+
response_body
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
@@ -11,10 +11,24 @@ module ApiModel
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def call_api(method, path, options={})
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
cache cache_id(path, options) do
|
15
|
+
request = HttpRequest.new path: path, method: method, config: api_model_configuration
|
16
|
+
request.builder = options.delete(:builder) || api_model_configuration.builder || self
|
17
|
+
request.options.merge! options
|
18
|
+
request.run.build_objects
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def cache_id(path, options={})
|
23
|
+
return @cache_id if @cache_id
|
24
|
+
p = (options[:params] || {}).collect{ |k,v| "#{k}#{v}" }.join("")
|
25
|
+
"#{path}#{p}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def cache(path, &block)
|
29
|
+
api_model_configuration.cache_strategy.new(path, api_model_configuration.cache_settings).cache do
|
30
|
+
block.call
|
31
|
+
end
|
18
32
|
end
|
19
33
|
|
20
34
|
end
|
@@ -127,4 +127,10 @@ describe ApiModel do
|
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
130
|
+
describe "cache_id" do
|
131
|
+
it 'should use options and the request path to create an identifier for the cache' do
|
132
|
+
BlogPost.cache_id("/box", params: { foo: "bar" }).should eq "/boxfoobar"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
130
136
|
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'support/mock_models/banana'
|
3
3
|
require 'support/mock_models/multiple_hosts'
|
4
|
+
require 'support/mock_models/blog_post'
|
4
5
|
|
5
6
|
describe ApiModel, "Configuration" do
|
6
7
|
|
7
8
|
after(:each) do
|
8
9
|
Banana.reset_api_configuration
|
10
|
+
BlogPost.reset_api_configuration
|
9
11
|
end
|
10
12
|
|
11
13
|
describe "api_host" do
|
@@ -60,6 +62,52 @@ describe ApiModel, "Configuration" do
|
|
60
62
|
end
|
61
63
|
end
|
62
64
|
|
65
|
+
describe "cache_strategy" do
|
66
|
+
it 'should default to NoCache' do
|
67
|
+
ApiModel::Base.api_model_configuration.cache_strategy.should eq ApiModel::CacheStrategies::NoCache
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "parser" do
|
72
|
+
it 'should default to the internal Json parser' do
|
73
|
+
ApiModel::Base.api_model_configuration.parser.should be_an_instance_of ApiModel::ResponseParser::Json
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should be used when handling api responses' do
|
77
|
+
ApiModel::ResponseParser::Json.any_instance.should_receive(:parse).with("{\"name\":\"foo\"}")
|
78
|
+
VCR.use_cassette('posts') { BlogPost.get_json "http://api-model-specs.com/single_post"}
|
79
|
+
end
|
80
|
+
|
81
|
+
class CustomParser
|
82
|
+
def parse(body)
|
83
|
+
{ name: "Hello world" }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should be possible to set a custom parser' do
|
88
|
+
BlogPost.api_config { |config| config.parser = CustomParser.new }
|
89
|
+
CustomParser.any_instance.should_receive(:parse).with("{\"name\":\"foo\"}")
|
90
|
+
VCR.use_cassette('posts') { BlogPost.get_json "http://api-model-specs.com/single_post"}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "builder" do
|
95
|
+
it 'should defult to nil' do
|
96
|
+
ApiModel::Base.api_model_configuration.builder.should be_nil
|
97
|
+
end
|
98
|
+
|
99
|
+
class CustomBuilder
|
100
|
+
def build(response)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should be possible to set a custom builder' do
|
105
|
+
BlogPost.api_config { |config| config.builder = CustomBuilder.new }
|
106
|
+
CustomBuilder.any_instance.should_receive(:build).with({ "name" => "foo"})
|
107
|
+
VCR.use_cassette('posts') { BlogPost.get_json "http://api-model-specs.com/single_post"}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
63
111
|
it 'should not unset other config values when you set a new one' do
|
64
112
|
ApiModel::Base.api_config { |c| c.host = "foo.com" }
|
65
113
|
Banana.api_config { |c| c.json_root = "banana" }
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ApiModel::ResponseParser::Json do
|
4
|
+
|
5
|
+
it "should produce a hash given valid json" do
|
6
|
+
ApiModel::ResponseParser::Json.new.parse("{\"name\":\"foo\"}")["name"].should eq "foo"
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should catch errors from parsing invalid json" do
|
10
|
+
ApiModel::Log.should_receive(:info).with "Could not parse JSON response: blah"
|
11
|
+
|
12
|
+
expect {
|
13
|
+
ApiModel::ResponseParser::Json.new.parse("blah")
|
14
|
+
}.to_not raise_error
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -11,19 +11,7 @@ describe ApiModel::Response do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
describe "parsing the json body" do
|
14
|
-
it "should produce a hash given valid json" do
|
15
|
-
valid_response.json_response_body.should be_a(Hash)
|
16
|
-
valid_response.json_response_body["name"].should eq "foo"
|
17
|
-
end
|
18
|
-
|
19
|
-
it "should catch errors from parsing invalid json" do
|
20
|
-
valid_response.stub_chain(:http_response, :api_call, :body).and_return "blah"
|
21
|
-
ApiModel::Log.should_receive(:info).with "Could not parse JSON response: blah"
|
22
14
|
|
23
|
-
expect {
|
24
|
-
valid_response.json_response_body
|
25
|
-
}.to_not raise_error
|
26
|
-
end
|
27
15
|
end
|
28
16
|
|
29
17
|
describe "using a custom json root on the response body" do
|
@@ -87,17 +75,17 @@ describe ApiModel::Response do
|
|
87
75
|
|
88
76
|
describe "#build_objects" do
|
89
77
|
let(:single_object) do
|
90
|
-
valid_response.stub(:
|
78
|
+
valid_response.stub(:response_body).and_return name: "foo"
|
91
79
|
valid_response.build_objects
|
92
80
|
end
|
93
81
|
|
94
82
|
let(:array_of_objects) do
|
95
|
-
valid_response.stub(:
|
83
|
+
valid_response.stub(:response_body).and_return [{name: "foo"}, {name: "bar"}]
|
96
84
|
valid_response.build_objects
|
97
85
|
end
|
98
86
|
|
99
87
|
let(:empty_response) do
|
100
|
-
valid_response.stub(:
|
88
|
+
valid_response.stub(:response_body).and_return nil
|
101
89
|
valid_response.build_objects
|
102
90
|
end
|
103
91
|
|
@@ -118,8 +106,8 @@ describe ApiModel::Response do
|
|
118
106
|
single_object.http_response.should be_a(ApiModel::HttpRequest)
|
119
107
|
end
|
120
108
|
|
121
|
-
it "should include the #
|
122
|
-
single_object.
|
109
|
+
it "should include the #response_body" do
|
110
|
+
single_object.response_body.should eq name: "foo"
|
123
111
|
end
|
124
112
|
|
125
113
|
it 'should return nil if the api returns an empty body' do
|
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.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Damien Timewell
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-12-
|
11
|
+
date: 2013-12-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -122,7 +122,9 @@ dependencies:
|
|
122
122
|
- - '='
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: 1.15.0
|
125
|
-
description:
|
125
|
+
description: API model is a simple wrapper for interacting with external APIs. It
|
126
|
+
tries to make it very simple and easy to make API calls and map the responses into
|
127
|
+
objects.
|
126
128
|
email:
|
127
129
|
- mail@damientimewell.com
|
128
130
|
executables: []
|
@@ -137,15 +139,18 @@ files:
|
|
137
139
|
- README.md
|
138
140
|
- api-model.gemspec
|
139
141
|
- lib/api-model.rb
|
142
|
+
- lib/api_model/cache_stategies/no_cache.rb
|
140
143
|
- lib/api_model/configuration.rb
|
141
144
|
- lib/api_model/http_request.rb
|
142
145
|
- lib/api_model/initializer.rb
|
143
146
|
- lib/api_model/response.rb
|
147
|
+
- lib/api_model/response_parser/json.rb
|
144
148
|
- lib/api_model/rest_methods.rb
|
145
149
|
- spec/api-model/api_model_spec.rb
|
146
150
|
- spec/api-model/configuration_spec.rb
|
147
151
|
- spec/api-model/http_request_spec.rb
|
148
152
|
- spec/api-model/initializer_spec.rb
|
153
|
+
- spec/api-model/json_response_parser_spec.rb
|
149
154
|
- spec/api-model/response_spec.rb
|
150
155
|
- spec/spec_helper.rb
|
151
156
|
- spec/support/fixtures/cars.yml
|
@@ -185,6 +190,7 @@ test_files:
|
|
185
190
|
- spec/api-model/configuration_spec.rb
|
186
191
|
- spec/api-model/http_request_spec.rb
|
187
192
|
- spec/api-model/initializer_spec.rb
|
193
|
+
- spec/api-model/json_response_parser_spec.rb
|
188
194
|
- spec/api-model/response_spec.rb
|
189
195
|
- spec/spec_helper.rb
|
190
196
|
- spec/support/fixtures/cars.yml
|