flexirest 1.3.35 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +146 -75
- data/flexirest.gemspec +2 -1
- data/lib/flexirest/associations.rb +20 -0
- data/lib/flexirest/base.rb +1 -1
- data/lib/flexirest/configuration.rb +25 -0
- data/lib/flexirest/json_api_proxy.rb +398 -0
- data/lib/flexirest/lazy_association_loader.rb +9 -3
- data/lib/flexirest/proxy_base.rb +1 -1
- data/lib/flexirest/request.rb +30 -4
- data/lib/flexirest/version.rb +1 -1
- data/lib/flexirest.rb +1 -0
- data/spec/lib/json_api_spec.rb +392 -0
- data/spec/lib/request_spec.rb +3 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 523560b561f09e067d5ccb4e00442091e821fc41
|
4
|
+
data.tar.gz: 6dbe9d04e9bb44e66c40c31c4b0e24cceea3d895
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6cef508630a6f7c15c9e8836a48ecbe6b4fd6ccd7a6c2f55cb9bb0e7a1bdb4bf0621159c0dcc31f65c8b9a3a5346d3304624bfccd23b2d51151760159872a8c3
|
7
|
+
data.tar.gz: 5152a2255c1719686cd48c64618c5cfc7ef56ecfc231ce1d18f58c7d0427da2348dc3c731c7621dce246dce8b3fae8f17f90e9456b22a5b1517e9d9c6a3b0830
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/flexirest/flexirest.svg)](http://isitmaintained.com/project/flexirest/flexirest "Average time to resolve an issue")
|
8
8
|
[![Percentage of issues still open](http://isitmaintained.com/badge/open/flexirest/flexirest.svg)](http://isitmaintained.com/project/flexirest/flexirest "Percentage of issues still open")
|
9
9
|
|
10
|
-
This gem is for accessing REST services in an ActiveRecord style.
|
10
|
+
This gem is for accessing REST services in an ActiveRecord style. ActiveResource already exists for this, but it doesn't work where the resource naming doesn't follow Rails conventions, it doesn't have in-built caching and it's not as flexible in general.
|
11
11
|
|
12
12
|
If you are a previous user of ActiveRestClient, there's some more information on [why I created this fork and how to upgrade](https://github.com/flexirest/flexirest/blob/master/Migrating-from-ActiveRestClient.md).
|
13
13
|
|
@@ -19,40 +19,42 @@ If you are a previous user of ActiveRestClient, there's some more information on
|
|
19
19
|
- [Get all people](#get-all-people)
|
20
20
|
- [Ruby on Rails Integration](#ruby-on-rails-integration)
|
21
21
|
- [Advanced Features](#advanced-features)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
22
|
+
- [Faraday Configuration](#faraday-configuration)
|
23
|
+
- [Associations](#associations)
|
24
|
+
- [Association Type 1 - Loading Other Classes](#association-type-1-loading-other-classes)
|
25
|
+
- [Association Type 2 - Lazy Loading From Other URLs](#association-type-2-lazy-loading-from-other-urls)
|
26
|
+
- [Association Type 3 - HAL Auto-loaded Resources](#association-type-3-hal-auto-loaded-resources)
|
27
|
+
- [Association Type 4 - Nested Resources](#association-type-4-nested-resources)
|
28
|
+
- [Association Type 5 - JSON API Auto-loaded Resources](#association-type-5-json-api-auto-loaded-resources)
|
29
|
+
- [Combined Example](#combined-example)
|
30
|
+
- [Caching](#caching)
|
31
|
+
- [Using callbacks](#using-callbacks)
|
32
|
+
- [Lazy Loading](#lazy-loading)
|
33
|
+
- [Authentication](#authentication)
|
34
|
+
- [Basic](#basic)
|
35
|
+
- [Api-Auth](#api-auth)
|
36
|
+
- [Body Types](#body-types)
|
37
|
+
- [Parallel Requests](#parallel-requests)
|
38
|
+
- [Faking Calls](#faking-calls)
|
39
|
+
- [Per-request Timeouts](#per-request-timeouts)
|
40
|
+
- [Per-request Params Encoding](#per-request-params-encoding)
|
41
|
+
- [Automatic Conversion of Fields to Date/DateTime](#automatic-conversion-of-fields-to-datedatetime)
|
42
|
+
- [Raw Requests](#raw-requests)
|
43
|
+
- [Plain Requests](#plain-requests)
|
44
|
+
- [JSON API](#json-api)
|
45
|
+
- [Proxying APIs](#proxying-apis)
|
46
|
+
- [Translating APIs](#translating-apis)
|
47
|
+
- [Default Parameters](#default-parameters)
|
48
|
+
- [Root element removal](#root-element-removal)
|
49
|
+
- [Required Parameters](#required-parameters)
|
50
|
+
- [Updating Only Changed/Dirty](#updating-only-changed-dirty)
|
51
|
+
- [HTTP/Parse Error Handling](#httpparse-error-handling)
|
52
|
+
- [Validation](#validation)
|
53
|
+
- [Permitting nil values](#permitting-nil-values)
|
51
54
|
- [Debugging](#debugging)
|
52
55
|
- [XML Responses](#xml-responses)
|
53
56
|
- [Contributing](#contributing)
|
54
57
|
|
55
|
-
|
56
58
|
## Installation
|
57
59
|
|
58
60
|
Add this line to your application's Gemfile:
|
@@ -63,11 +65,15 @@ gem 'flexirest'
|
|
63
65
|
|
64
66
|
And then execute:
|
65
67
|
|
66
|
-
|
68
|
+
```
|
69
|
+
$ bundle
|
70
|
+
```
|
67
71
|
|
68
72
|
Or install it yourself as:
|
69
73
|
|
70
|
-
|
74
|
+
```
|
75
|
+
$ gem install flexirest
|
76
|
+
```
|
71
77
|
|
72
78
|
## Usage
|
73
79
|
|
@@ -91,7 +97,7 @@ class Person < Flexirest::Base
|
|
91
97
|
end
|
92
98
|
```
|
93
99
|
|
94
|
-
Note I've specified the base_url in the class above.
|
100
|
+
Note I've specified the base_url in the class above. This is useful where you want to be explicit or use different APIs for some classes and be explicit. If you have one server that's generally used, you can set it once with a simple line in a `config/initializer/{something}.rb` file:
|
95
101
|
|
96
102
|
```ruby
|
97
103
|
Flexirest::Base.base_url = "https://www.example.com/api/v1"
|
@@ -132,7 +138,7 @@ If an API returns an array of results and you have [will_paginate](https://rubyg
|
|
132
138
|
end
|
133
139
|
```
|
134
140
|
|
135
|
-
Note, you can assign to any attribute, whether it exists or not before and read from any attribute (which will return nil if not found).
|
141
|
+
Note, you can assign to any attribute, whether it exists or not before and read from any attribute (which will return nil if not found). If you pass a string or a number to a method it will assume that it's for the "id" field. Any other field values must be passed as a hash and you can't mix passing a string/number and a hash.
|
136
142
|
|
137
143
|
```ruby
|
138
144
|
@person = Person.find(1234) # valid
|
@@ -142,7 +148,7 @@ Note, you can assign to any attribute, whether it exists or not before and read
|
|
142
148
|
@person = Person.find(1234, :name => "Billy") # invalid
|
143
149
|
```
|
144
150
|
|
145
|
-
You can also call any mapped method as an instance variable which will pass the current attribute set in as parameters (either GET or POST depending on the mapped method type).
|
151
|
+
You can also call any mapped method as an instance variable which will pass the current attribute set in as parameters (either GET or POST depending on the mapped method type). If the method returns a single instance it will assign the attributes of the calling object and return itself. If the method returns a list of instances, it will only return the list. So, we could rewrite the create call above as:
|
146
152
|
|
147
153
|
```ruby
|
148
154
|
@person = Person.new
|
@@ -154,7 +160,7 @@ puts @person.id
|
|
154
160
|
|
155
161
|
The response of the #create call set the attributes at that point (any manually set attributes before that point are removed).
|
156
162
|
|
157
|
-
If you have attributes beginning with a number, Ruby doesn't like this.
|
163
|
+
If you have attributes beginning with a number, Ruby doesn't like this. So, you can use hash style notation to read/write the attributes:
|
158
164
|
|
159
165
|
```ruby
|
160
166
|
@tv = Tv.find(model:"UE55U8000") # { "properties" : {"3d" : false} }
|
@@ -162,7 +168,7 @@ puts @tv.properties["3d"]
|
|
162
168
|
@tv.properties["3d"] = true
|
163
169
|
```
|
164
170
|
|
165
|
-
If you want to debug the response, using inspect on the response object may well be useful.
|
171
|
+
If you want to debug the response, using inspect on the response object may well be useful. However, if you want a simpler output, then you can call `#to_json` on the response object:
|
166
172
|
|
167
173
|
```ruby
|
168
174
|
@person = Person.find(email:"something@example.com")
|
@@ -187,7 +193,7 @@ Flexirest::Base.adapter = :patron
|
|
187
193
|
|
188
194
|
In versions before 1.2.0 the adapter was hardcoded to `:patron`, so if you want to ensure it still uses Patron, you should set this setting.
|
189
195
|
|
190
|
-
If you want more control you can pass a **complete** configuration block ("complete" means that the block does not
|
196
|
+
If you want more control you can pass a **complete** configuration block ("complete" means that the block does not _override_ [the default configuration](https://github.com/flexirest/flexirest/blob/5b1953d89e26c02ca74f74464ccb7cd4c9439dcc/lib/flexirest/configuration.rb#L184-L201), but rather _replaces_ it). For available config variables look into the Faraday documentation.
|
191
197
|
|
192
198
|
```ruby
|
193
199
|
Flexirest::Base.faraday_config do |faraday|
|
@@ -196,13 +202,14 @@ Flexirest::Base.faraday_config do |faraday|
|
|
196
202
|
faraday.headers['User-Agent'] = "Flexirest/#{Flexirest::VERSION}"
|
197
203
|
end
|
198
204
|
```
|
205
|
+
|
199
206
|
### Associations
|
200
207
|
|
201
|
-
There are two types of association.
|
208
|
+
There are two types of association. One assumes when you call a method you actually want it to call the method on a separate class (as that class has other methods that are useful). The other is lazy loading related classes from a separate URL.
|
202
209
|
|
203
210
|
#### Association Type 1 - Loading Other Classes
|
204
211
|
|
205
|
-
If the call would return a single instance or a list of instances that should be considered another object, you can also specify this when mapping the method using the `:has_one` or `:has_many` options respectively.
|
212
|
+
If the call would return a single instance or a list of instances that should be considered another object, you can also specify this when mapping the method using the `:has_one` or `:has_many` options respectively. It doesn't call anything on that object except for instantiate it, but it does let you have objects of a different class to the one you initially called.
|
206
213
|
|
207
214
|
```ruby
|
208
215
|
class Expense < Flexirest::Base
|
@@ -226,7 +233,7 @@ puts @person.expenses.reduce {|e| e.inc_vat}
|
|
226
233
|
puts @person.address.full_string
|
227
234
|
```
|
228
235
|
|
229
|
-
You can also use `has_one`/`has_many` on the class level to allow chaining of classes.
|
236
|
+
You can also use `has_one`/`has_many` on the class level to allow chaining of classes. You can specify the class name or allow the system to automatically convert it to the singular class. For example:
|
230
237
|
|
231
238
|
```ruby
|
232
239
|
class Expense < Flexirest::Base
|
@@ -283,7 +290,7 @@ When the `:array` option includes an attribute, it is assumed the values were re
|
|
283
290
|
|
284
291
|
#### Association Type 2 - Lazy Loading From Other URLs
|
285
292
|
|
286
|
-
When mapping the method, passing a list of attributes will cause any requests for those attributes to mapped to the URLs given in their responses.
|
293
|
+
When mapping the method, passing a list of attributes will cause any requests for those attributes to mapped to the URLs given in their responses. The response for the attribute may be one of the following:
|
287
294
|
|
288
295
|
```ruby
|
289
296
|
"attribute" : "URL"
|
@@ -293,9 +300,9 @@ When mapping the method, passing a list of attributes will cause any requests fo
|
|
293
300
|
"attribute" : { "something" : "URL"}
|
294
301
|
```
|
295
302
|
|
296
|
-
The difference between the last 3 examples is that a key of `url` or `href` signifies it's a single object that is lazy loaded from the value specified.
|
303
|
+
The difference between the last 3 examples is that a key of `url` or `href` signifies it's a single object that is lazy loaded from the value specified. Any other keys assume that it's a nested set of URLs (like in the array situation, but accessible via the keys - e.g. object.attribute.something in the above example).
|
297
304
|
|
298
|
-
It is required that the URL is a complete URL including a protocol starting with "http".
|
305
|
+
It is required that the URL is a complete URL including a protocol starting with "http". To configure this use code like:
|
299
306
|
|
300
307
|
```ruby
|
301
308
|
class Person < Flexirest::Base
|
@@ -316,9 +323,9 @@ And use it like this:
|
|
316
323
|
|
317
324
|
#### Association Type 3 - HAL Auto-loaded Resources
|
318
325
|
|
319
|
-
You don't need to define lazy attributes if they are defined using [HAL](http://stateless.co/hal_specification.html) (with an optional embedded representation).
|
326
|
+
You don't need to define lazy attributes if they are defined using [HAL](http://stateless.co/hal_specification.html) (with an optional embedded representation). If your resource has an `_links` item (and optionally an `_embedded` item) then it will automatically treat the linked resources (with the `_embedded` cache) as if they were defined using `:lazy` as per type 2 above.
|
320
327
|
|
321
|
-
If you need to, you can access properties of the HAL association.
|
328
|
+
If you need to, you can access properties of the HAL association. By default just using the HAL association gets the embedded resource (or requests the remote resource if not available in the `_embedded` list).
|
322
329
|
|
323
330
|
```ruby
|
324
331
|
@person = Person.find(1)
|
@@ -350,9 +357,31 @@ Ad.all(magazine_id: 1)
|
|
350
357
|
Ad.create(magazine_id: 1, title: "My Add Title")
|
351
358
|
```
|
352
359
|
|
360
|
+
#### Association Type 5 - JSON API Auto-loaded Resources
|
361
|
+
|
362
|
+
If attributes are defined using [JSON API](http://jsonapi.org), you don't need to define lazy attributes. If your resource has a `links` object with a `related` item, it will automatically treat the linked resources as if they were defined using `:lazy`.
|
363
|
+
|
364
|
+
You need to activate JSON API by specifying the `json_api` proxy:
|
365
|
+
|
366
|
+
```ruby
|
367
|
+
class Article < Flexirest::Base
|
368
|
+
proxy :json_api
|
369
|
+
end
|
370
|
+
```
|
371
|
+
|
372
|
+
If you want to embed linked resources directly in the response (i.e. request a JSON API compound document), use the `includes` class method. The linked resource is accessed in the same was as if it was lazily loaded, but without the extra request:
|
373
|
+
|
374
|
+
```ruby
|
375
|
+
# Makes a call to /articles with parameters: include=images
|
376
|
+
Article.includes(:images).all
|
377
|
+
|
378
|
+
# For nested resources, the include parameter becomes: include=images.tags,images.photographer
|
379
|
+
Article.includes(:images => [:tags, :photographer]).all
|
380
|
+
```
|
381
|
+
|
353
382
|
#### Combined Example
|
354
383
|
|
355
|
-
OK, so let's say you have an API for getting articles.
|
384
|
+
OK, so let's say you have an API for getting articles. Each article has a property called `title` (which is a string) and a property `images` which includes a list of URIs. Following this URI would take you to a image API that returns the image's `filename` and `filesize`. We'll also assume this is a HAL compliant API. We would declare our two models (one for articles and one for images) like the following:
|
356
385
|
|
357
386
|
```ruby
|
358
387
|
class Article < Flexirest::Base
|
@@ -401,7 +430,7 @@ When it comes time to use it, you would do something like this:
|
|
401
430
|
end
|
402
431
|
```
|
403
432
|
|
404
|
-
At this point, only the HTTP call to '/articles/1' has been made.
|
433
|
+
At this point, only the HTTP call to '/articles/1' has been made. When you actually start using properties of the images list/image object then it makes a call to the URL given in the images list and you can use the properties as if it was a nested JSON object in the original response instead of just a URL:
|
405
434
|
|
406
435
|
```ruby
|
407
436
|
@image = @article.images.first
|
@@ -411,7 +440,7 @@ puts @image.filesize
|
|
411
440
|
# => 123456
|
412
441
|
```
|
413
442
|
|
414
|
-
You can also treat `@image` looks like an Image class (and you should 100% treat it as one) it's technically a lazy loading proxy.
|
443
|
+
You can also treat `@image` looks like an Image class (and you should 100% treat it as one) it's technically a lazy loading proxy. So, if you cache the views for your application should only make HTTP API requests when actually necessary.
|
415
444
|
|
416
445
|
```ruby
|
417
446
|
puts @image.nice_size
|
@@ -442,7 +471,7 @@ Flexirest::Base.cache_store = Redis::Store.new("redis://localhost:6379/0/cache")
|
|
442
471
|
|
443
472
|
### Using callbacks
|
444
473
|
|
445
|
-
You can use callbacks to alter get/post parameters, the URL or set the post body (doing so overrides normal parameter insertion in to the body) before a request or to adjust the response after a request.
|
474
|
+
You can use callbacks to alter get/post parameters, the URL or set the post body (doing so overrides normal parameter insertion in to the body) before a request or to adjust the response after a request. This can either be a block or a named method (like ActionController's `before_callback`/`before_action` methods).
|
446
475
|
|
447
476
|
The callback is passed the name of the method (e.g. `:save`) and an object (a request object for `before_request` and a response object for `after_request`). The request object has four public attributes `post_params` (a Hash of the POST parameters), `get_params` (a Hash of the GET parameters), `headers` and `url` (a `String` containing the full URL without GET parameters appended).
|
448
477
|
|
@@ -549,7 +578,6 @@ end
|
|
549
578
|
|
550
579
|
This will return an array of the named method for each object or the response from the block and will have loaded the objects in to the resource.
|
551
580
|
|
552
|
-
|
553
581
|
### Authentication
|
554
582
|
|
555
583
|
#### Basic
|
@@ -578,6 +606,7 @@ Flexirest::Base.api_auth_credentials(@access_id, @secret_key)
|
|
578
606
|
```
|
579
607
|
|
580
608
|
You can also specify different credentials for different models just like configuring base_url
|
609
|
+
|
581
610
|
```ruby
|
582
611
|
class Person < Flexirest::Base
|
583
612
|
api_auth_credentials('123456', 'abcdef')
|
@@ -596,7 +625,7 @@ end
|
|
596
625
|
|
597
626
|
### Body Types
|
598
627
|
|
599
|
-
By default Flexirest puts the body in to normal CGI parameters in K=V&K2=V2 format.
|
628
|
+
By default Flexirest puts the body in to normal CGI parameters in K=V&K2=V2 format. However, if you want to use JSON for your PUT/POST requests, you can use either (the other option, the default, is `:form_encoded`):
|
600
629
|
|
601
630
|
```ruby
|
602
631
|
class Person < Flexirest::Base
|
@@ -626,12 +655,14 @@ end
|
|
626
655
|
### Parallel Requests
|
627
656
|
|
628
657
|
Sometimes you know you will need to make a bunch of requests and you don't want to wait for one to finish to start the next. When using parallel requests there is the potential to finish many requests all at the same time taking only as long as the single longest request. To use parallel requests you will need to set Flexirest to use a Faraday adapter that supports parallel requests [(such as Typhoeus)](https://github.com/lostisland/faraday/wiki/Parallel-requests).
|
658
|
+
|
629
659
|
```ruby
|
630
660
|
# Set adapter to Typhoeus to use parallel requests
|
631
661
|
Flexirest::Base.adapter = :typhoeus
|
632
662
|
```
|
633
663
|
|
634
664
|
Now you just need to get ahold of the connection that is going to make the requests by specifying the same host that the models will be using. When inside the `in_parallel` block call request methods as usual and access the results after the `in_parallel` block ends.
|
665
|
+
|
635
666
|
```ruby
|
636
667
|
Flexirest::ConnectionManager.in_parallel('https://www.example.com') do
|
637
668
|
@person = Person.find(1234)
|
@@ -647,7 +678,7 @@ puts @employers.size #=> 7
|
|
647
678
|
|
648
679
|
### Faking Calls
|
649
680
|
|
650
|
-
There are times when an API hasn't been developed yet, so you want to fake the API call response.
|
681
|
+
There are times when an API hasn't been developed yet, so you want to fake the API call response. To do this, you can simply pass a `fake` option when mapping the call containing the response.
|
651
682
|
|
652
683
|
```ruby
|
653
684
|
class Person < Flexirest::Base
|
@@ -655,7 +686,7 @@ class Person < Flexirest::Base
|
|
655
686
|
end
|
656
687
|
```
|
657
688
|
|
658
|
-
You may want to run a proc when faking data (to put information from the parameters in to the response or return different responses depending on the parameters).
|
689
|
+
You may want to run a proc when faking data (to put information from the parameters in to the response or return different responses depending on the parameters). To do this just pass a proc to :fake:
|
659
690
|
|
660
691
|
```ruby
|
661
692
|
class Person < Flexirest::Base
|
@@ -665,7 +696,7 @@ end
|
|
665
696
|
|
666
697
|
### Per-request Timeouts
|
667
698
|
|
668
|
-
There are times when an API is generally quick, but one call is very intensive.
|
699
|
+
There are times when an API is generally quick, but one call is very intensive. You don't want to set a global timeout in the Faraday configuration block, you just want to increase the timeout for this single call. To do this, you can simply pass a `timeout` option when mapping the call containing the response (in seconds).
|
669
700
|
|
670
701
|
```ruby
|
671
702
|
class Person < Flexirest::Base
|
@@ -703,7 +734,7 @@ would output the following url
|
|
703
734
|
|
704
735
|
### Automatic Conversion of Fields to Date/DateTime
|
705
736
|
|
706
|
-
By default Flexirest will attempt to convert all fields to a Date or DateTime object if it's a string and the value matches a pair of regular expressions. However, on large responses this can be computationally expensive.
|
737
|
+
By default Flexirest will attempt to convert all fields to a Date or DateTime object if it's a string and the value matches a pair of regular expressions. However, on large responses this can be computationally expensive. You can disable this automatic conversion completely with:
|
707
738
|
|
708
739
|
```ruby
|
709
740
|
Flexirest::Base.disable_automatic_date_parsing = true
|
@@ -734,7 +765,7 @@ This system respects `disable_automatic_date_parsing`, and will default to mappi
|
|
734
765
|
|
735
766
|
### Raw Requests
|
736
767
|
|
737
|
-
Sometimes you have have a URL that you just want to force through, but have the response handled in the same way as normal objects or you want to have the callbacks run (say for authentication).
|
768
|
+
Sometimes you have have a URL that you just want to force through, but have the response handled in the same way as normal objects or you want to have the callbacks run (say for authentication). The easiest way to do that is to call `_request` on the class:
|
738
769
|
|
739
770
|
```ruby
|
740
771
|
class Person < Flexirest::Base
|
@@ -746,13 +777,13 @@ people = Person._request('http://api.example.com/v1/people') # Defaults to get w
|
|
746
777
|
Person._request('http://api.example.com/v1/people', :post, {id:1234,name:"John"}) # Post with parameters
|
747
778
|
```
|
748
779
|
|
749
|
-
If you want to use a lazy loaded request instead (so it will create an object that will only call the API if you use it), you can use `_lazy_request` instead of `_request`.
|
780
|
+
If you want to use a lazy loaded request instead (so it will create an object that will only call the API if you use it), you can use `_lazy_request` instead of `_request`. If you want you can create a construct that creates and object that lazy loads itself from a given method (rather than a URL):
|
750
781
|
|
751
782
|
```ruby
|
752
783
|
@person = Person._lazy_request(Person._request_for(:find, 1234))
|
753
784
|
```
|
754
785
|
|
755
|
-
This initially creates an Flexirest::Request object as if you'd called `Person.find(1234)` which is then passed in to the `_lazy_request` method to return an object that will call the request if any properties are actually used.
|
786
|
+
This initially creates an Flexirest::Request object as if you'd called `Person.find(1234)` which is then passed in to the `_lazy_request` method to return an object that will call the request if any properties are actually used. This may be useful at some point, but it's actually easier to just prefix the `find` method call with `lazy_` like:
|
756
787
|
|
757
788
|
```ruby
|
758
789
|
@person = Person.lazy_find(1234)
|
@@ -786,9 +817,47 @@ end
|
|
786
817
|
|
787
818
|
The response of a plain request (from either source) is a `Flexirest::PlainResponse` which acts like a string containing the response's body, but it also has a `_headers` method that returns the HTTP response headers and a `_status` method containing the response's HTTP method.
|
788
819
|
|
820
|
+
### JSON API
|
821
|
+
|
822
|
+
If you are working with a [JSON API](http://jsonapi.org), you need to activate JSON API by specifying the `json_api` proxy:
|
823
|
+
|
824
|
+
```ruby
|
825
|
+
class Article < Flexirest::Base
|
826
|
+
proxy :json_api
|
827
|
+
end
|
828
|
+
```
|
829
|
+
|
830
|
+
This proxy translates requests according to the JSON API specifications, parses responses, and retrieves linked resources. It also adds the `Accept: application/vnd.api+json` header for all requests.
|
831
|
+
|
832
|
+
It supports lazy loading by default. Unless a compound document is returned from the connected JSON API service, it will make another request to the service for the specified linked resource.
|
833
|
+
|
834
|
+
To reduce the number of requests to the service, you can ask the service to include the linked resources in the response. Such responses are called "compound documents". To do this, use the `includes` method:
|
835
|
+
|
836
|
+
```ruby
|
837
|
+
# Makes a call to /articles with parameters: include=images
|
838
|
+
Article.includes(:images).all
|
839
|
+
|
840
|
+
# For nested resources, the include parameter becomes: include=images.tags,images.photographer
|
841
|
+
Article.includes(:images => [:tags, :photographer]).all
|
842
|
+
```
|
843
|
+
|
844
|
+
For post and patch requests, the proxy formats a JSON API complied request, and adds a `Content-Type: application/vnd.api+json` header. It guesses the `type` value in the resource object from the class name, but it can be set specifically with `alias_type`:
|
845
|
+
|
846
|
+
```ruby
|
847
|
+
class Photographer < Flexirest::Base
|
848
|
+
proxy :json_api
|
849
|
+
# Sets the type in the resource object to "people"
|
850
|
+
alias_type :people
|
851
|
+
|
852
|
+
patch :update, '/photographers/:id'
|
853
|
+
end
|
854
|
+
```
|
855
|
+
|
856
|
+
NB: Updating relationships is not yet supported.
|
857
|
+
|
789
858
|
### Proxying APIs
|
790
859
|
|
791
|
-
Sometimes you may be working with an old API that returns JSON in a less than ideal format or the URL or parameters required have changed.
|
860
|
+
Sometimes you may be working with an old API that returns JSON in a less than ideal format or the URL or parameters required have changed. In this case you can define a descendent of `Flexirest::ProxyBase`, pass it to your model as the proxy and have it rework URLs/parameters on the way out and the response on the way back in (already converted to a Ruby hash/array). By default any non-proxied URLs are just passed through to the underlying connection layer. For example:
|
792
861
|
|
793
862
|
```ruby
|
794
863
|
class ArticleProxy < Flexirest::ProxyBase
|
@@ -815,8 +884,8 @@ Article.all.first_name == "Billy"
|
|
815
884
|
|
816
885
|
This example does two things:
|
817
886
|
|
818
|
-
1. It rewrites the incoming URL for any requests matching "
|
819
|
-
2. It uses the `translate` method to move the "fname" attribute from the response body to be called "first_name".
|
887
|
+
1. It rewrites the incoming URL for any requests matching "_/all_" to "/all_people"
|
888
|
+
2. It uses the `translate` method to move the "fname" attribute from the response body to be called "first_name". The translate method must return the new object at the end (either the existing object alterered, or a new object to replace it with)
|
820
889
|
|
821
890
|
As the comment shows, you can use `url value` to set the request URL to a particular value, or you can call `gsub!` on the url to replace parts of it using more complicated regular expressions.
|
822
891
|
|
@@ -829,7 +898,7 @@ get "/list" do
|
|
829
898
|
end
|
830
899
|
```
|
831
900
|
|
832
|
-
This example renames the get_parameter for the request from `identifier` to `id` (the same would have worked with post_params if it was a POST/PUT request).
|
901
|
+
This example renames the get_parameter for the request from `identifier` to `id` (the same would have worked with post_params if it was a POST/PUT request). The `passthrough` method will take care of automatically recombining them in to the URL or encoding them in to the body as appropriate.
|
833
902
|
|
834
903
|
If you want to manually set the body for the API yourself you can use the `body` method
|
835
904
|
|
@@ -848,7 +917,7 @@ The proxy block expects one of three things to be the return value of the block.
|
|
848
917
|
2. The second option is to save the response from `passthrough` and use `translate` on it to alter the structure.
|
849
918
|
3. The third option is to use `render` if you want to completely fake an API and return the JSON yourself
|
850
919
|
|
851
|
-
To completely fake the API, you can do the following.
|
920
|
+
To completely fake the API, you can do the following. Note, this is also achievable using the `fake` setting when mapping a method, however by doing it in a Proxy block means you can dynamically generate the JSON rather than just a hard coded string.
|
852
921
|
|
853
922
|
```ruby
|
854
923
|
put "/fake" do
|
@@ -858,9 +927,9 @@ end
|
|
858
927
|
|
859
928
|
### Translating APIs
|
860
929
|
|
861
|
-
**IMPORTANT: This functionality has been deprecated in favour of the "Proxying APIs" functionality above.
|
930
|
+
**IMPORTANT: This functionality has been deprecated in favour of the "Proxying APIs" functionality above. You should aim to remove this from your code as soon as possible.**
|
862
931
|
|
863
|
-
Sometimes you may be working with an API that returns JSON in a less than ideal format.
|
932
|
+
Sometimes you may be working with an API that returns JSON in a less than ideal format. In this case you can define a barebones class and pass it to your model. The Translator class must have class methods that are passed the JSON object and should return an object in the correct format. It doesn't need to have a method unless it's going to translate that mapping though (so in the example below there's no list method). For example:
|
864
933
|
|
865
934
|
```ruby
|
866
935
|
class ArticleTranslator
|
@@ -934,7 +1003,7 @@ class Person < Flexirest::Base
|
|
934
1003
|
get :all, '/people', :requires => [:active]
|
935
1004
|
end
|
936
1005
|
|
937
|
-
@people = Person.all #
|
1006
|
+
@people = Person.all # raises Flexirest::MissingParametersException
|
938
1007
|
@people = Person.all(active:false)
|
939
1008
|
```
|
940
1009
|
|
@@ -966,13 +1035,14 @@ end
|
|
966
1035
|
|
967
1036
|
#### Additional Notes:
|
968
1037
|
|
969
|
-
|
970
|
-
|
971
|
-
|
1038
|
+
- The above examples specifically showed PATCH methods, but this is also available for POST and PUT methods for flexibility purposes (even though they break typical REST methodology).
|
1039
|
+
- This logic is currently evaluated before Required Parameters, so it is possible to ensure that requirements are met by some clever usage.
|
1040
|
+
|
1041
|
+
- This means that if a method is `:requires => [:active], :only_changed => {active: false}` then `active` will always have a value and would always pass the `:requires` directive (so you need to be very careful because the answer may end up being `nil` if you didn't specifically set it).
|
972
1042
|
|
973
1043
|
### HTTP/Parse Error Handling
|
974
1044
|
|
975
|
-
Sometimes the backend server may respond with a non-200/304 header, in which case the code will raise an `Flexirest::HTTPClientException` for 4xx errors or an `Flexirest::HTTPServerException` for 5xx errors.
|
1045
|
+
Sometimes the backend server may respond with a non-200/304 header, in which case the code will raise an `Flexirest::HTTPClientException` for 4xx errors or an `Flexirest::HTTPServerException` for 5xx errors. These both have a `status` accessor and a `result` accessor (for getting access to the parsed body):
|
976
1046
|
|
977
1047
|
```ruby
|
978
1048
|
begin
|
@@ -986,7 +1056,7 @@ If the response is unparsable (e.g. not in the desired content type), then it wi
|
|
986
1056
|
|
987
1057
|
### Validation
|
988
1058
|
|
989
|
-
You can create validations on your objects just like Rails' built in ActiveModel validations.
|
1059
|
+
You can create validations on your objects just like Rails' built in ActiveModel validations. For example:
|
990
1060
|
|
991
1061
|
```ruby
|
992
1062
|
class Person < Flexirest::Base
|
@@ -1021,6 +1091,7 @@ validates :name, :presence, message: "must be given please"
|
|
1021
1091
|
```
|
1022
1092
|
|
1023
1093
|
#### Permitting nil values
|
1094
|
+
|
1024
1095
|
The default behavior for `:length`, `:numericality` and `:inclusion` validators is to fail when a `nil` value is encountered. You can prevent `nil` attribute values from triggering validation errors for attributes that may permit `nil` by adding the `:allow_nil => true` option. Adding this option with a `true` value to `:length`, `:numericality` and `:inclusion` validators will permit `nil` values and not trigger errors. Some examples are:
|
1025
1096
|
|
1026
1097
|
```ruby
|
@@ -1082,7 +1153,7 @@ class Person < Flexirest::Base
|
|
1082
1153
|
end
|
1083
1154
|
```
|
1084
1155
|
|
1085
|
-
By default verbose logging isn't enabled, so it's up to the developer to enable it (and remember to disable it afterwards).
|
1156
|
+
By default verbose logging isn't enabled, so it's up to the developer to enable it (and remember to disable it afterwards). It does use debug level logging, so it shouldn't fill up a correctly configured production server anyway.
|
1086
1157
|
|
1087
1158
|
If you prefer to record the output of an API call in a more automated fashion you can use a callback called `record_response` like this:
|
1088
1159
|
|
@@ -1098,7 +1169,7 @@ end
|
|
1098
1169
|
|
1099
1170
|
## XML Responses
|
1100
1171
|
|
1101
|
-
Flexirest uses Crack to allow parsing of XML responses.
|
1172
|
+
Flexirest uses Crack to allow parsing of XML responses. For example, given an XML response of (with a content type of `application/xml` or `text/xml`):
|
1102
1173
|
|
1103
1174
|
```xml
|
1104
1175
|
<?xml version="1.0" encoding="utf-8"?>
|
data/flexirest.gemspec
CHANGED
@@ -24,10 +24,11 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_development_dependency "rspec", "~> 3"
|
25
25
|
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.1.0')
|
26
26
|
spec.add_development_dependency "webmock", "~> 2.1.0"
|
27
|
+
spec.add_development_dependency "rspec_junit_formatter", "= 0.2.3"
|
27
28
|
else
|
28
29
|
spec.add_development_dependency "webmock"
|
30
|
+
spec.add_development_dependency "rspec_junit_formatter"
|
29
31
|
end
|
30
|
-
spec.add_development_dependency "rspec_junit_formatter"
|
31
32
|
spec.add_development_dependency "simplecov"
|
32
33
|
spec.add_development_dependency "simplecov-rcov"
|
33
34
|
spec.add_development_dependency 'coveralls'
|
@@ -51,6 +51,19 @@ module Flexirest
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
def includes(*keys)
|
55
|
+
@_include_associations = keys
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def _include_associations
|
60
|
+
@_include_associations
|
61
|
+
end
|
62
|
+
|
63
|
+
def _reset_include_associations!
|
64
|
+
@_include_associations = []
|
65
|
+
end
|
66
|
+
|
54
67
|
def parse_date(*keys)
|
55
68
|
keys.each { |key| @_date_fields << key }
|
56
69
|
end
|
@@ -59,8 +72,15 @@ module Flexirest
|
|
59
72
|
@_date_fields.uniq
|
60
73
|
end
|
61
74
|
|
75
|
+
def _associations
|
76
|
+
@_associations
|
77
|
+
end
|
78
|
+
|
79
|
+
|
62
80
|
def inherited(subclass)
|
63
81
|
subclass.instance_variable_set(:@_date_fields, [])
|
82
|
+
subclass.instance_variable_set(:@_associations, {})
|
83
|
+
subclass.instance_variable_set(:@_include_associations, [])
|
64
84
|
super
|
65
85
|
end
|
66
86
|
end
|