flexirest 1.9.17 → 1.10.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/docs/filtering-result-lists.md +1 -1
- data/docs/json-api.md +16 -4
- data/docs/plain-requests.md +1 -1
- data/docs/proxying-apis.md +1 -1
- data/docs/raw-requests.md +1 -1
- data/docs/root-elements.md +41 -6
- data/docs/using-callbacks.md +28 -1
- data/flexirest.gemspec +1 -8
- data/lib/flexirest/base_without_validation.rb +16 -0
- data/lib/flexirest/configuration.rb +26 -0
- data/lib/flexirest/json_api_proxy.rb +1 -4
- data/lib/flexirest/lazy_association_loader.rb +4 -0
- data/lib/flexirest/request.rb +56 -15
- data/lib/flexirest/result_iterator.rb +4 -2
- data/lib/flexirest/version.rb +1 -1
- data/spec/lib/base_without_validation_spec.rb +30 -0
- data/spec/lib/json_api_spec.rb +40 -6
- data/spec/lib/request_spec.rb +142 -3
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53b34d9600101b9d80a61559054a3599242a7739b12845df02bde410c5858d94
|
4
|
+
data.tar.gz: e7398bcc97a8a0f89fb3380110e126e482d5fc1e14a973dd9e34208f28634814
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32014dfe143032f040ae140cdcf562f05832f74d0b05e692b8f866d4636363b6f7ffcbe0a4de0e7a895c8b1722aef0f6736f34c130916dc5c02869caa198434a
|
7
|
+
data.tar.gz: a7f3cb00fd3451c4f06ef32bf6e148f90d4046c1cce7526a8bfe6c247e2d65dcd68a0d924f85683ef052929432ddf341768578a7591b37db8b7729ed96053c80
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,35 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.10.3
|
4
|
+
|
5
|
+
Enhancement:
|
6
|
+
|
7
|
+
- Ignore/wrap root functionality available for all routes, rather than just route specifically (thanks to Sampat Badhe for the PR)
|
8
|
+
|
9
|
+
## 1.10.2
|
10
|
+
|
11
|
+
Bugfix:
|
12
|
+
|
13
|
+
- JSON-API calls do not include linked resources when no other parameter is passed (thanks to Stevo-S for the bug report and François Ferrandis for the PR)
|
14
|
+
|
15
|
+
## 1.10.1
|
16
|
+
|
17
|
+
Enhancement:
|
18
|
+
|
19
|
+
- Nested objects now report their dirty/changed status up to the parent (thanks to Matthias Hähnel for the bug report)
|
20
|
+
|
21
|
+
## 1.10.0
|
22
|
+
|
23
|
+
Enhancement:
|
24
|
+
|
25
|
+
- Add specific exceptions for the most common 5xx server-side errors
|
26
|
+
|
27
|
+
## 1.9.18
|
28
|
+
|
29
|
+
Security:
|
30
|
+
|
31
|
+
- Upgrade rest-client development dependency to a CVE-fixed version.
|
32
|
+
|
3
33
|
## 1.9.17
|
4
34
|
|
5
35
|
Feature:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# *Flexirest:* Filtering result lists
|
2
2
|
|
3
|
-
If the API returns a JSON list of items, this is
|
3
|
+
If the API returns a JSON list of items, this is returned to you as a `Flexirest::ResultIterator` object. A `ResultIterator` sorts simple filtering of the list using a `where` method based on values matching a specified criteria (or matching using regular expressions):
|
4
4
|
|
5
5
|
```ruby
|
6
6
|
class Article < Flexirest::Base
|
data/docs/json-api.md
CHANGED
@@ -12,17 +12,26 @@ This proxy translates requests according to the JSON API specifications, parses
|
|
12
12
|
|
13
13
|
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.
|
14
14
|
|
15
|
+
|
16
|
+
## Including associations
|
17
|
+
|
15
18
|
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:
|
16
19
|
|
17
20
|
```ruby
|
18
|
-
# Makes a call to /articles
|
21
|
+
# Makes a call to: /articles?include=images
|
19
22
|
Article.includes(:images).all
|
20
23
|
|
21
|
-
#
|
24
|
+
# Fetch nested resources: /articles?include=images.tags,images.photographer
|
22
25
|
Article.includes(:images => [:tags, :photographer]).all
|
26
|
+
|
27
|
+
# Note: the `includes` method takes precedence over the passed `:include` parameter.
|
28
|
+
# This will result in query: /articles?include=images
|
29
|
+
Article.includes(:images).all(include: "author")
|
23
30
|
```
|
24
31
|
|
25
|
-
|
32
|
+
## Resource type
|
33
|
+
|
34
|
+
The `type` value is guessed from the class name, but it can be set specifically with `alias_type`:
|
26
35
|
|
27
36
|
```ruby
|
28
37
|
class Photographer < Flexirest::Base
|
@@ -34,7 +43,10 @@ class Photographer < Flexirest::Base
|
|
34
43
|
end
|
35
44
|
```
|
36
45
|
|
37
|
-
|
46
|
+
|
47
|
+
## Notes
|
48
|
+
|
49
|
+
Updating relationships is not yet supported.
|
38
50
|
|
39
51
|
|
40
52
|
-----
|
data/docs/plain-requests.md
CHANGED
@@ -7,7 +7,7 @@ class Person < Flexirest::Base
|
|
7
7
|
end
|
8
8
|
|
9
9
|
people = Person._plain_request('http://api.example.com/v1/people') # Defaults to get with no parameters
|
10
|
-
# people is a
|
10
|
+
# people is a string containing the response
|
11
11
|
|
12
12
|
Person._plain_request('http://api.example.com/v1/people', :post, {id:1234,name:"John"}) # Post with parameters
|
13
13
|
```
|
data/docs/proxying-apis.md
CHANGED
@@ -40,7 +40,7 @@ Article.all.first_name == "Billy"
|
|
40
40
|
This example does two things:
|
41
41
|
|
42
42
|
1. It rewrites the incoming URL for any requests matching "_/all_" to "/all_people"
|
43
|
-
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
|
43
|
+
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 altered, or a new object to replace it with)
|
44
44
|
|
45
45
|
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.
|
46
46
|
|
data/docs/raw-requests.md
CHANGED
@@ -12,7 +12,7 @@ people = Person._request('http://api.example.com/v1/people') # Defaults to get w
|
|
12
12
|
Person._request('http://api.example.com/v1/people', :post, {id:1234,name:"John"}) # Post with parameters
|
13
13
|
```
|
14
14
|
|
15
|
-
When you need to specify custom headers (for example for authentication) you can do this with a fourth option to the `_request` method. If you are using the default
|
15
|
+
When you need to specify custom headers (for example for authentication) you can do this with a fourth option to the `_request` method. If you are using the default parameters you'll need to specify them. For example:
|
16
16
|
|
17
17
|
```ruby
|
18
18
|
Person._request("http://api.example.com/v1/people", :get, {}, {headers:{"X-Something": "foo/bar"}})
|
data/docs/root-elements.md
CHANGED
@@ -2,13 +2,32 @@
|
|
2
2
|
|
3
3
|
If your response comes back with a root node and you'd like to ignore it, you can define the mapping as:
|
4
4
|
|
5
|
+
```ruby
|
6
|
+
Flexirest::Base.ignore_root = "data"
|
7
|
+
```
|
8
|
+
|
9
|
+
Any `ignore_root` setting in specific class overrides this declared default.
|
10
|
+
|
5
11
|
```ruby
|
6
12
|
class Feed < Flexirest::Base
|
7
|
-
|
13
|
+
ignore_root: "feed"
|
14
|
+
|
15
|
+
post :list, "/feed"
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
And any `ignore_root` setting in specific request overrides the both default and class specific setting.
|
20
|
+
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
class Feed < Flexirest::Base
|
24
|
+
ignore_root: 'feed'
|
25
|
+
|
26
|
+
post :list, "/feed", ignore_root: "result"
|
8
27
|
end
|
9
28
|
```
|
10
29
|
|
11
|
-
|
30
|
+
You can also assign an array to `ignore_root` if you'd want to remove a tree of root nodes.
|
12
31
|
|
13
32
|
```ruby
|
14
33
|
class Feed < Flexirest::Base
|
@@ -26,16 +45,32 @@ Alternatively if you want to wrap your JSON request body in a root element, e.g.
|
|
26
45
|
}
|
27
46
|
```
|
28
47
|
|
29
|
-
You can do it
|
48
|
+
You can do it using `wrap_root`:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
Flexirest::Base.wrap_root = "data"
|
52
|
+
```
|
53
|
+
|
54
|
+
Any `wrap_root` setting in specific class overrides this declared default.
|
30
55
|
|
31
56
|
```ruby
|
32
57
|
class Feed < Flexirest::Base
|
33
|
-
|
34
|
-
end
|
58
|
+
wrap_root: "feed"
|
35
59
|
|
36
|
-
|
60
|
+
post :list, "/feed"
|
61
|
+
end
|
37
62
|
```
|
38
63
|
|
64
|
+
And any `wrap_root` setting in specific request overrides the both default and class specific setting.
|
65
|
+
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
class Feed < Flexirest::Base
|
69
|
+
wrap_root: 'feed'
|
70
|
+
|
71
|
+
post :list, "/feed", wrap_root: "data"
|
72
|
+
end
|
73
|
+
```
|
39
74
|
|
40
75
|
-----
|
41
76
|
|
data/docs/using-callbacks.md
CHANGED
@@ -66,6 +66,7 @@ After callbacks work in exactly the same way:
|
|
66
66
|
```ruby
|
67
67
|
class Person < Flexirest::Base
|
68
68
|
get :all, "/people"
|
69
|
+
get :all, "/people"
|
69
70
|
|
70
71
|
after_request :fix_empty_content
|
71
72
|
after_request :cache_all_people
|
@@ -88,6 +89,32 @@ end
|
|
88
89
|
|
89
90
|
**Note:** since v1.3.21 the empty response trick above isn't necessary, empty responses for 204 are accepted normally (the method returns `true`), but this is here to show an example of an `after_request` callback adjusting the body. The `cache_all_people` example shows how to cache a response even if the server doesn't send the correct headers.
|
90
91
|
|
92
|
+
If you manually set caching responses like the above, you may want to invalidate that cache when you make a create, update, delete etc request for the resource, for example:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
class Animal < Flexirest::Base
|
96
|
+
after_request :cache
|
97
|
+
before_request :cache_cleanup
|
98
|
+
|
99
|
+
get :get, '/animals/:id'
|
100
|
+
post :update, '/animals/:id'
|
101
|
+
delete :delete, '/animals/:id'
|
102
|
+
|
103
|
+
def cache_cleanup(name, request)
|
104
|
+
if name == :update || name == :delete
|
105
|
+
Flexirest::Logger.info(" \033[1;4;32m#{Flexirest.name}\033[0m Invalidating cache for #{self.class.name} #{request.url}")
|
106
|
+
Rails.cache.delete("#{self.class.name}:#{request.url}")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def cache(name, response)
|
111
|
+
if name == :get
|
112
|
+
response.response_headers["Expires"] = 1.hour.from_now.iso8601
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
91
118
|
If you want to trap an error in an `after_request` callback and retry the request, this can be done - but retries will only happen once for each request (so we'd recommend checking all conditions in a single `after_request` and then retrying after fixing them all). You achieve this by raising a `Flexirest::CallbackRetryRequestException` from the callback.
|
92
119
|
|
93
120
|
```ruby
|
@@ -100,7 +127,7 @@ class Person < Flexirest::Base
|
|
100
127
|
|
101
128
|
def fix_invalid_request(name, response)
|
102
129
|
if response.status == 401
|
103
|
-
# Do something to fix the state of caches/variables used in the
|
130
|
+
# Do something to fix the state of caches/variables used in the
|
104
131
|
# before_request, etc
|
105
132
|
raise Flexirest::CallbackRetryRequestException.new
|
106
133
|
end
|
data/flexirest.gemspec
CHANGED
@@ -39,6 +39,7 @@ Gem::Specification.new do |spec|
|
|
39
39
|
spec.add_development_dependency "api-auth", ">= 1.3.1", "< 2.4"
|
40
40
|
spec.add_development_dependency 'typhoeus'
|
41
41
|
spec.add_development_dependency 'activemodel'
|
42
|
+
spec.add_development_dependency 'rest-client'
|
42
43
|
|
43
44
|
spec.add_runtime_dependency "mime-types"
|
44
45
|
spec.add_runtime_dependency "multi_json"
|
@@ -52,12 +53,4 @@ Gem::Specification.new do |spec|
|
|
52
53
|
else
|
53
54
|
spec.add_runtime_dependency "activesupport", "< 5.0.0"
|
54
55
|
end
|
55
|
-
# JSON is an implicit dependency of something, but JSON v2+ requires Ruby 2+
|
56
|
-
# Same with "tins" which is a dependency of coveralls
|
57
|
-
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
58
|
-
spec.add_runtime_dependency "json", "< 2.0.0"
|
59
|
-
spec.add_runtime_dependency "tins", "~> 1.6.0"
|
60
|
-
spec.add_runtime_dependency "term-ansicolor", "~> 1.3.2"
|
61
|
-
spec.add_runtime_dependency "public_suffix", "~> 1.4.6"
|
62
|
-
end
|
63
56
|
end
|
@@ -11,6 +11,8 @@ module Flexirest
|
|
11
11
|
attr_accessor :_status
|
12
12
|
attr_accessor :_etag
|
13
13
|
attr_accessor :_headers
|
14
|
+
attr_accessor :_parent
|
15
|
+
attr_accessor :_parent_attribute_name
|
14
16
|
|
15
17
|
instance_methods.each do |m|
|
16
18
|
next unless %w{display presence load require untrust trust freeze method enable_warnings with_warnings suppress capture silence quietly debugger breakpoint}.map(&:to_sym).include? m
|
@@ -41,6 +43,13 @@ module Flexirest
|
|
41
43
|
def _copy_from(result)
|
42
44
|
@attributes = result._attributes
|
43
45
|
@_status = result._status
|
46
|
+
self._parent = result._parent
|
47
|
+
self._parent_attribute_name = result._parent_attribute_name
|
48
|
+
@attributes.each do |k,v|
|
49
|
+
if v.respond_to?(:_parent) && v._parent.present?
|
50
|
+
@attributes[k]._parent = self
|
51
|
+
end
|
52
|
+
end
|
44
53
|
_clean!
|
45
54
|
end
|
46
55
|
|
@@ -184,6 +193,10 @@ module Flexirest
|
|
184
193
|
output.to_json
|
185
194
|
end
|
186
195
|
|
196
|
+
def _set_dirty(key)
|
197
|
+
@dirty_attributes[key.to_sym] = true
|
198
|
+
end
|
199
|
+
|
187
200
|
private
|
188
201
|
|
189
202
|
def _set_attribute(key, value)
|
@@ -191,6 +204,9 @@ module Flexirest
|
|
191
204
|
old_value = @attributes[key.to_sym] unless old_value
|
192
205
|
old_value = old_value[0] if old_value and old_value.is_a? Array
|
193
206
|
@dirty_attributes[key.to_sym] = [old_value, value] if old_value != value
|
207
|
+
if _parent
|
208
|
+
_parent._set_dirty(_parent_attribute_name)
|
209
|
+
end
|
194
210
|
@attributes[key.to_sym] = value
|
195
211
|
end
|
196
212
|
|
@@ -13,6 +13,32 @@ module Flexirest
|
|
13
13
|
@api_auth_access_id = nil
|
14
14
|
@api_auth_secret_key = nil
|
15
15
|
@api_auth_options = {}
|
16
|
+
@ignore_root = nil
|
17
|
+
@wrap_root = nil
|
18
|
+
|
19
|
+
def ignore_root(value=nil)
|
20
|
+
if value.nil?
|
21
|
+
value = if @ignore_root.nil? && superclass.respond_to?(:ignore_root)
|
22
|
+
superclass.ignore_root
|
23
|
+
else
|
24
|
+
@ignore_root
|
25
|
+
end
|
26
|
+
else
|
27
|
+
@ignore_root = value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def wrap_root(value=nil)
|
32
|
+
if value.nil?
|
33
|
+
value = if @wrap_root.nil? && superclass.respond_to?(:wrap_root)
|
34
|
+
superclass.wrap_root
|
35
|
+
else
|
36
|
+
@wrap_root
|
37
|
+
end
|
38
|
+
else
|
39
|
+
@wrap_root = value
|
40
|
+
end
|
41
|
+
end
|
16
42
|
|
17
43
|
def base_url(value = nil)
|
18
44
|
@base_url ||= nil
|
@@ -50,11 +50,8 @@ module Flexirest
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def translate(params, include_associations)
|
53
|
-
# Return to caller if nothing is to be done
|
54
|
-
return params unless params.present? && include_associations.present?
|
55
|
-
|
56
53
|
# Format the linked resources array, and assign to include key
|
57
|
-
params[:include] = format_include_params(include_associations)
|
54
|
+
params[:include] = format_include_params(include_associations) if include_associations.present?
|
58
55
|
end
|
59
56
|
|
60
57
|
private
|
@@ -94,6 +94,10 @@ module Flexirest
|
|
94
94
|
request = Flexirest::Request.new(method, @request.object)
|
95
95
|
request.url = request.forced_url = @url
|
96
96
|
@object = request.call
|
97
|
+
if @object.respond_to?(:_parent=)
|
98
|
+
@object._parent = @options[:parent]
|
99
|
+
@object._parent_attribute_name = @options[:parent_attribute_name]
|
100
|
+
end
|
97
101
|
end
|
98
102
|
end
|
99
103
|
end
|
data/lib/flexirest/request.rb
CHANGED
@@ -136,6 +136,30 @@ module Flexirest
|
|
136
136
|
end
|
137
137
|
end
|
138
138
|
|
139
|
+
def ignore_root
|
140
|
+
if @method[:options][:ignore_root]
|
141
|
+
@method[:options][:ignore_root]
|
142
|
+
elsif @object.nil?
|
143
|
+
nil
|
144
|
+
elsif object_is_class?
|
145
|
+
@object.ignore_root
|
146
|
+
else
|
147
|
+
@object.class.ignore_root
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def wrap_root
|
152
|
+
if @method[:options][:wrap_root]
|
153
|
+
@method[:options][:wrap_root]
|
154
|
+
elsif @object.nil?
|
155
|
+
nil
|
156
|
+
elsif object_is_class?
|
157
|
+
@object.wrap_root
|
158
|
+
else
|
159
|
+
@object.class.wrap_root
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
139
163
|
def verbose?
|
140
164
|
if object_is_class?
|
141
165
|
@object.verbose
|
@@ -438,8 +462,8 @@ module Flexirest
|
|
438
462
|
@post_params
|
439
463
|
else
|
440
464
|
p = (params || @post_params || {})
|
441
|
-
if
|
442
|
-
p = {
|
465
|
+
if wrap_root.present?
|
466
|
+
p = {wrap_root => p}
|
443
467
|
end
|
444
468
|
p.to_query
|
445
469
|
end
|
@@ -452,8 +476,8 @@ module Flexirest
|
|
452
476
|
@post_params
|
453
477
|
else
|
454
478
|
p = (params || @post_params || {})
|
455
|
-
if
|
456
|
-
p = {
|
479
|
+
if wrap_root.present?
|
480
|
+
p = {wrap_root => p}
|
457
481
|
end
|
458
482
|
data, mp_headers = Flexirest::Multipart::Post.prepare_query(p)
|
459
483
|
mp_headers.each do |k,v|
|
@@ -467,8 +491,8 @@ module Flexirest
|
|
467
491
|
elsif @post_params.is_a?(String)
|
468
492
|
@post_params
|
469
493
|
else
|
470
|
-
if
|
471
|
-
{
|
494
|
+
if wrap_root.present?
|
495
|
+
{wrap_root => (params || @post_params || {})}.to_json
|
472
496
|
else
|
473
497
|
(params || @post_params || {}).to_json
|
474
498
|
end
|
@@ -601,7 +625,7 @@ module Flexirest
|
|
601
625
|
else
|
602
626
|
Flexirest::Logger.debug " \033[1;4;32m#{Flexirest.name}\033[0m #{@instrumentation_name} - Response received #{@response.body.size} bytes"
|
603
627
|
end
|
604
|
-
result = generate_new_object(ignore_root:
|
628
|
+
result = generate_new_object(ignore_root: ignore_root, ignore_xml_root: @method[:options][:ignore_xml_root])
|
605
629
|
# TODO: Cleanup when ignore_xml_root is removed
|
606
630
|
else
|
607
631
|
raise ResponseParseException.new(status:status, body:@response.body, headers: @response.headers)
|
@@ -630,6 +654,16 @@ module Flexirest
|
|
630
654
|
raise HTTPConflictClientException.new(status:status, result:error_response, raw_response: @response.body, url:@url, method: http_method)
|
631
655
|
elsif status == 429
|
632
656
|
raise HTTPTooManyRequestsClientException.new(status:status, result:error_response, raw_response: @response.body, url:@url, method: http_method)
|
657
|
+
elsif status == 500
|
658
|
+
raise HTTPInternalServerException.new(status:status, result:error_response, raw_response: @response.body, url:@url, method: http_method)
|
659
|
+
elsif status == 501
|
660
|
+
raise HTTPNotImplementedServerException.new(status:status, result:error_response, raw_response: @response.body, url:@url, method: http_method)
|
661
|
+
elsif status == 502
|
662
|
+
raise HTTPBadGatewayServerException.new(status:status, result:error_response, raw_response: @response.body, url:@url, method: http_method)
|
663
|
+
elsif status == 503
|
664
|
+
raise HTTPServiceUnavailableServerException.new(status:status, result:error_response, raw_response: @response.body, url:@url, method: http_method)
|
665
|
+
elsif status == 504
|
666
|
+
raise HTTPGatewayTimeoutServerException.new(status:status, result:error_response, raw_response: @response.body, url:@url, method: http_method)
|
633
667
|
elsif (400..499).include? status
|
634
668
|
raise HTTPClientException.new(status:status, result:error_response, raw_response: @response.body, url:@url, method: http_method)
|
635
669
|
elsif (500..599).include? status
|
@@ -641,7 +675,7 @@ module Flexirest
|
|
641
675
|
result
|
642
676
|
end
|
643
677
|
|
644
|
-
def new_object(attributes, name = nil)
|
678
|
+
def new_object(attributes, name = nil, parent = nil, parent_attribute_name = nil)
|
645
679
|
@method[:options][:has_many] ||= {}
|
646
680
|
name = name.to_sym rescue nil
|
647
681
|
if @method[:options][:has_many][name]
|
@@ -654,6 +688,9 @@ module Flexirest
|
|
654
688
|
object = create_object_instance
|
655
689
|
end
|
656
690
|
|
691
|
+
object._parent = parent
|
692
|
+
object._parent_attribute_name = parent_attribute_name
|
693
|
+
|
657
694
|
if hal_response? && name.nil?
|
658
695
|
attributes = handle_hal_links_embedded(object, attributes)
|
659
696
|
end
|
@@ -681,9 +718,9 @@ module Flexirest
|
|
681
718
|
v = value
|
682
719
|
assignable_hash = value_from_object ? object._attributes : {}
|
683
720
|
if value_from_object && @method[:options][:lazy].include?(k)
|
684
|
-
assignable_hash[k] = Flexirest::LazyAssociationLoader.new(overridden_name, v, self, overridden_name:(overridden_name))
|
721
|
+
assignable_hash[k] = Flexirest::LazyAssociationLoader.new(overridden_name, v, self, overridden_name:(overridden_name), parent: object, parent_attribute_name: k)
|
685
722
|
elsif v.is_a? Hash
|
686
|
-
assignable_hash[k] = new_object(v, overridden_name )
|
723
|
+
assignable_hash[k] = new_object(v, overridden_name, object, k)
|
687
724
|
elsif v.is_a? Array
|
688
725
|
if @method[:options][:array].include?(k)
|
689
726
|
assignable_hash[k] = Array.new
|
@@ -801,15 +838,15 @@ module Flexirest
|
|
801
838
|
body = JsonAPIProxy::Response.parse(body, @object)
|
802
839
|
end
|
803
840
|
|
804
|
-
if
|
805
|
-
[
|
841
|
+
if ignore_root
|
842
|
+
[ignore_root].flatten.each do |key|
|
806
843
|
body = body[key.to_s]
|
807
844
|
end
|
808
845
|
end
|
809
846
|
elsif is_xml_response?
|
810
847
|
body = @response.body.blank? ? {} : Crack::XML.parse(@response.body)
|
811
|
-
if
|
812
|
-
[
|
848
|
+
if ignore_root
|
849
|
+
[ignore_root].flatten.each do |key|
|
813
850
|
body = body[key.to_s]
|
814
851
|
end
|
815
852
|
elsif options[:ignore_xml_root]
|
@@ -901,5 +938,9 @@ module Flexirest
|
|
901
938
|
class HTTPNotFoundClientException < HTTPClientException ; end
|
902
939
|
class HTTPTooManyRequestsClientException < HTTPClientException ; end
|
903
940
|
class HTTPServerException < HTTPException ; end
|
904
|
-
|
941
|
+
class HTTPInternalServerException < HTTPServerException ; end
|
942
|
+
class HTTPNotImplementedServerException < HTTPServerException ; end
|
943
|
+
class HTTPBadGatewayServerException < HTTPServerException ; end
|
944
|
+
class HTTPServiceUnavailableServerException < HTTPServerException ; end
|
945
|
+
class HTTPGatewayTimeoutServerException < HTTPServerException ; end
|
905
946
|
end
|
@@ -4,6 +4,8 @@ module Flexirest
|
|
4
4
|
|
5
5
|
attr_accessor :_status, :items
|
6
6
|
attr_reader :_headers
|
7
|
+
attr_accessor :_parent
|
8
|
+
attr_accessor :_parent_attribute_name
|
7
9
|
|
8
10
|
def initialize(response = nil)
|
9
11
|
@_status = response.try :status
|
@@ -66,8 +68,8 @@ module Flexirest
|
|
66
68
|
self
|
67
69
|
end
|
68
70
|
|
69
|
-
def delete_if
|
70
|
-
@items = @items.delete_if &
|
71
|
+
def delete_if(&block)
|
72
|
+
@items = @items.delete_if &block
|
71
73
|
self
|
72
74
|
end
|
73
75
|
|
data/lib/flexirest/version.rb
CHANGED
@@ -12,6 +12,11 @@ class TranslatorExample
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
class NestedExample < Flexirest::BaseWithoutValidation
|
16
|
+
get :all, "/all", fake:"{\"person\":{\"name\": \"Billy\"}}"
|
17
|
+
end
|
18
|
+
|
19
|
+
|
15
20
|
class AlteringClientExample < Flexirest::BaseWithoutValidation
|
16
21
|
translator TranslatorExample
|
17
22
|
base_url "http://www.example.com"
|
@@ -140,6 +145,31 @@ describe Flexirest::BaseWithoutValidation do
|
|
140
145
|
expect(client).to be_dirty
|
141
146
|
end
|
142
147
|
|
148
|
+
it "should store attribute changes on nested objects and mark them as dirty in the parent" do
|
149
|
+
client = NestedExample.all
|
150
|
+
client.person.name = "John"
|
151
|
+
expect(client.person.name.to_s).to eq("John")
|
152
|
+
|
153
|
+
expect(client).to be_dirty
|
154
|
+
expect(client.changes.keys).to include(:person)
|
155
|
+
|
156
|
+
expect(client.person).to be_dirty
|
157
|
+
expect(client.person.changes.keys).to include(:name)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should store attribute changes on nested objects loaded via instance methods and mark them as dirty in the parent" do
|
161
|
+
client = NestedExample.new()
|
162
|
+
client.all
|
163
|
+
client.person.name = "John"
|
164
|
+
expect(client.person.name.to_s).to eq("John")
|
165
|
+
|
166
|
+
expect(client).to be_dirty
|
167
|
+
expect(client.changes.keys).to include(:person)
|
168
|
+
|
169
|
+
expect(client.person).to be_dirty
|
170
|
+
expect(client.person.changes.keys).to include(:name)
|
171
|
+
end
|
172
|
+
|
143
173
|
it "should track changed attributes and provide access to previous values (similar to ActiveRecord/Mongoid)" do
|
144
174
|
client = EmptyExample.new()
|
145
175
|
client["test"] = "Something"
|
data/spec/lib/json_api_spec.rb
CHANGED
@@ -303,7 +303,7 @@ describe 'JSON API' do
|
|
303
303
|
let(:tags) { JsonAPIExample::Tag }
|
304
304
|
let(:author) { JsonAPIExample::Author }
|
305
305
|
|
306
|
-
|
306
|
+
describe 'responses' do
|
307
307
|
it 'should return the data object if the response contains only one data instance' do
|
308
308
|
expect(subject.find(1)).to be_an_instance_of(JsonAPIExampleArticle)
|
309
309
|
end
|
@@ -370,17 +370,17 @@ describe 'JSON API' do
|
|
370
370
|
end
|
371
371
|
end
|
372
372
|
|
373
|
-
|
373
|
+
describe 'attributes' do
|
374
374
|
it 'should return the attributes as part of the data instance' do
|
375
|
-
expect(subject.find(1).item).
|
375
|
+
expect(subject.find(1).item).to eq("item one")
|
376
376
|
end
|
377
377
|
|
378
378
|
it 'should return the association\'s attributes as part of the association instance' do
|
379
|
-
expect(subject.includes(:author).find_single_author(1).author.item).
|
379
|
+
expect(subject.includes(:author).find_single_author(1).author.item).to eq("item two")
|
380
380
|
end
|
381
381
|
end
|
382
382
|
|
383
|
-
|
383
|
+
describe 'associations' do
|
384
384
|
it 'should retrieve the resource\'s associations via its relationships object' do
|
385
385
|
expect(subject.includes(:tags).find(1).tags.size).to eq(2)
|
386
386
|
end
|
@@ -415,6 +415,40 @@ describe 'JSON API' do
|
|
415
415
|
end
|
416
416
|
end
|
417
417
|
|
418
|
+
describe 'requests' do
|
419
|
+
describe 'the `include=` parameter' do
|
420
|
+
before { stub_request(:get, %r{example\.com/articles}) }
|
421
|
+
|
422
|
+
context 'when using `.includes(:tags)`' do
|
423
|
+
it 'equal "tags"' do
|
424
|
+
JsonAPIExample::Article.includes(:tags).real_index
|
425
|
+
expect(WebMock).to have_requested(:get, 'http://www.example.com/articles?include=tags')
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
context 'when using `.includes(tags: [:authors, :articles])`' do
|
430
|
+
it 'equal "tags.authors,tags.articles"' do
|
431
|
+
JsonAPIExample::Article.includes(tags: [:authors, :articles]).real_index
|
432
|
+
expect(WebMock).to have_requested(:get, 'http://www.example.com/articles?include=tags.authors,tags.articles')
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
context 'when using both `.includes(:tags)` and other params in the final call' do
|
437
|
+
it 'uses the values passed to the `includes() method`' do
|
438
|
+
JsonAPIExample::Article.includes(:tags).real_index(filter: { author_id: 4 })
|
439
|
+
expect(WebMock).to have_requested(:get, 'http://www.example.com/articles?filter%5Bauthor_id%5D=4&include=tags')
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
context 'when using both `.includes(:tags)` and the :include param in final call' do
|
444
|
+
it 'uses the values passed to the `includes() method`' do
|
445
|
+
JsonAPIExample::Article.includes(:tags).real_index(include: "author")
|
446
|
+
expect(WebMock).to have_requested(:get, 'http://www.example.com/articles?include=tags')
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
418
452
|
context 'lazy loading' do
|
419
453
|
it 'should fetch association lazily' do
|
420
454
|
stub_request(:get, /www.example.com\/articles\/1\/tags/)
|
@@ -434,7 +468,7 @@ describe 'JSON API' do
|
|
434
468
|
end
|
435
469
|
end
|
436
470
|
|
437
|
-
|
471
|
+
describe 'client' do
|
438
472
|
it 'should request with json api format, and expect a json api response' do
|
439
473
|
expect_any_instance_of(Flexirest::Connection).to receive(:post) { |_, _, _, options|
|
440
474
|
expect(options[:headers]).to include('Content-Type' => 'application/vnd.api+json')
|
data/spec/lib/request_spec.rb
CHANGED
@@ -199,6 +199,89 @@ describe Flexirest::Request do
|
|
199
199
|
}
|
200
200
|
end
|
201
201
|
|
202
|
+
class LocalIgnoredRootExampleClient < ExampleClient
|
203
|
+
ignore_root "feed"
|
204
|
+
|
205
|
+
get :root, "/root", fake: %Q{
|
206
|
+
{
|
207
|
+
"feed": {
|
208
|
+
"title": "Example Feed"
|
209
|
+
}
|
210
|
+
}
|
211
|
+
}
|
212
|
+
end
|
213
|
+
|
214
|
+
class LocalIgnoredMultiLevelRootExampleClient < ExampleClient
|
215
|
+
ignore_root [:response, "data", "object"]
|
216
|
+
|
217
|
+
get :multi_level_root, "/multi-level-root", fake: %Q{
|
218
|
+
{
|
219
|
+
"response": {
|
220
|
+
"data": {
|
221
|
+
"object": {
|
222
|
+
"title": "Example Multi Level Feed"
|
223
|
+
}
|
224
|
+
}
|
225
|
+
}
|
226
|
+
}
|
227
|
+
}
|
228
|
+
end
|
229
|
+
|
230
|
+
class BaseIgnoredRootExampleClient < Flexirest::Base
|
231
|
+
base_url "http://www.example.com"
|
232
|
+
ignore_root "feed"
|
233
|
+
end
|
234
|
+
|
235
|
+
class GlobalIgnoredRootExampleClient < BaseIgnoredRootExampleClient
|
236
|
+
get :root, "/root", fake: %Q{
|
237
|
+
{
|
238
|
+
"feed": {
|
239
|
+
"title": "Example Feed"
|
240
|
+
}
|
241
|
+
}
|
242
|
+
}
|
243
|
+
end
|
244
|
+
|
245
|
+
class OverrideGlobalIgnoredRootForFileExampleClient < BaseIgnoredRootExampleClient
|
246
|
+
ignore_root "data"
|
247
|
+
|
248
|
+
get :root, "/root", fake: %Q{
|
249
|
+
{
|
250
|
+
"data": {
|
251
|
+
"title": "Example Feed"
|
252
|
+
}
|
253
|
+
}
|
254
|
+
}
|
255
|
+
end
|
256
|
+
|
257
|
+
class OverrideGlobalIgnoredRootForRequestExampleClient < BaseIgnoredRootExampleClient
|
258
|
+
get :root, "/root", ignore_root: "data", fake: %Q{
|
259
|
+
{
|
260
|
+
"data": {
|
261
|
+
"title": "Example Feed"
|
262
|
+
}
|
263
|
+
}
|
264
|
+
}
|
265
|
+
end
|
266
|
+
|
267
|
+
class BaseWrappedRootExampleClient < Flexirest::Base
|
268
|
+
base_url "http://www.example.com"
|
269
|
+
wrap_root "base_data"
|
270
|
+
end
|
271
|
+
|
272
|
+
class GlobalWrappedRootExampleClient < BaseWrappedRootExampleClient
|
273
|
+
put :wrapped, "/put/:id"
|
274
|
+
end
|
275
|
+
|
276
|
+
class OverrideGlobalWrappedRootForFileExampleClient < BaseWrappedRootExampleClient
|
277
|
+
wrap_root "class_specific_data"
|
278
|
+
put :wrapped, "/put/:id"
|
279
|
+
end
|
280
|
+
|
281
|
+
class OverrideGlobalWrappedRootForRequestExampleClient < BaseWrappedRootExampleClient
|
282
|
+
put :wrapped, "/put/:id", wrap_root: "request_specific_data"
|
283
|
+
end
|
284
|
+
|
202
285
|
class WhitelistedDateClient < Flexirest::Base
|
203
286
|
base_url "http://www.example.com"
|
204
287
|
put :conversion, "/put/:id"
|
@@ -1005,7 +1088,7 @@ describe Flexirest::Request do
|
|
1005
1088
|
rescue Flexirest::HTTPServerException => e
|
1006
1089
|
e
|
1007
1090
|
end
|
1008
|
-
expect(e).to be_instance_of(Flexirest::
|
1091
|
+
expect(e).to be_instance_of(Flexirest::HTTPInternalServerException)
|
1009
1092
|
expect(e.status).to eq(500)
|
1010
1093
|
expect(e.result.first_name).to eq("John")
|
1011
1094
|
end
|
@@ -1021,7 +1104,7 @@ describe Flexirest::Request do
|
|
1021
1104
|
rescue Flexirest::HTTPServerException => e
|
1022
1105
|
e
|
1023
1106
|
end
|
1024
|
-
expect(e.message).to eq(%q{The POST to '/create' returned a 500 status, which raised a Flexirest::
|
1107
|
+
expect(e.message).to eq(%q{The POST to '/create' returned a 500 status, which raised a Flexirest::HTTPInternalServerException with a body of: \{"first_name":"John", "id":1234\}})
|
1025
1108
|
end
|
1026
1109
|
|
1027
1110
|
it "should raise a parse exception for invalid JSON returns" do
|
@@ -1036,7 +1119,7 @@ describe Flexirest::Request do
|
|
1036
1119
|
rescue => e
|
1037
1120
|
e
|
1038
1121
|
end
|
1039
|
-
expect(e).to be_instance_of(Flexirest::
|
1122
|
+
expect(e).to be_instance_of(Flexirest::HTTPInternalServerException)
|
1040
1123
|
expect(e.status).to eq(500)
|
1041
1124
|
expect(e.result).to eq(error_content)
|
1042
1125
|
end
|
@@ -1355,6 +1438,62 @@ describe Flexirest::Request do
|
|
1355
1438
|
expect(IgnoredMultiLevelRootExampleClient.multi_level_root.title).to eq("Example Multi Level Feed")
|
1356
1439
|
end
|
1357
1440
|
|
1441
|
+
it "should ignore a specified root element" do
|
1442
|
+
expect(LocalIgnoredRootExampleClient.root.title).to eq("Example Feed")
|
1443
|
+
end
|
1444
|
+
|
1445
|
+
it "should ignore a specified multi-level root element" do
|
1446
|
+
expect(LocalIgnoredMultiLevelRootExampleClient.multi_level_root.title).to eq("Example Multi Level Feed")
|
1447
|
+
end
|
1448
|
+
|
1449
|
+
it "should ignore a specified root element" do
|
1450
|
+
expect(GlobalIgnoredRootExampleClient.root.title).to eq("Example Feed")
|
1451
|
+
end
|
1452
|
+
|
1453
|
+
it "should ignore a specified root element" do
|
1454
|
+
expect(OverrideGlobalIgnoredRootForFileExampleClient.root.title).to eq("Example Feed")
|
1455
|
+
end
|
1456
|
+
|
1457
|
+
it "should ignore a specified root element" do
|
1458
|
+
expect(OverrideGlobalIgnoredRootForRequestExampleClient.root.title).to eq("Example Feed")
|
1459
|
+
end
|
1460
|
+
|
1461
|
+
it "should wrap elements if specified, in form-encoded format" do
|
1462
|
+
expect_any_instance_of(Flexirest::Connection).to receive(:put).with("/put/1234", %q(base_data%5Bdebug%5D=true&base_data%5Btest%5D=foo), an_instance_of(Hash)).and_return(::FaradayResponseMock.new(OpenStruct.new(body:"{\"result\":true}", response_headers:{})))
|
1463
|
+
GlobalWrappedRootExampleClient.request_body_type :form_encoded
|
1464
|
+
GlobalWrappedRootExampleClient.wrapped id:1234, debug:true, test:'foo'
|
1465
|
+
end
|
1466
|
+
|
1467
|
+
it "should wrap elements if specified, in form-encoded format" do
|
1468
|
+
expect_any_instance_of(Flexirest::Connection).to receive(:put).with("/put/1234", %q(class_specific_data%5Bdebug%5D=true&class_specific_data%5Btest%5D=foo), an_instance_of(Hash)).and_return(::FaradayResponseMock.new(OpenStruct.new(body:"{\"result\":true}", response_headers:{})))
|
1469
|
+
OverrideGlobalWrappedRootForFileExampleClient.request_body_type :form_encoded
|
1470
|
+
OverrideGlobalWrappedRootForFileExampleClient.wrapped id:1234, debug:true, test:'foo'
|
1471
|
+
end
|
1472
|
+
|
1473
|
+
it "should wrap elements if specified, in form-encoded format" do
|
1474
|
+
expect_any_instance_of(Flexirest::Connection).to receive(:put).with("/put/1234", %q(request_specific_data%5Bdebug%5D=true&request_specific_data%5Btest%5D=foo), an_instance_of(Hash)).and_return(::FaradayResponseMock.new(OpenStruct.new(body:"{\"result\":true}", response_headers:{})))
|
1475
|
+
OverrideGlobalWrappedRootForRequestExampleClient.request_body_type :form_encoded
|
1476
|
+
OverrideGlobalWrappedRootForRequestExampleClient.wrapped id:1234, debug:true, test:'foo'
|
1477
|
+
end
|
1478
|
+
|
1479
|
+
it "should encode the body wrapped in a root element in a JSON format if specified" do
|
1480
|
+
expect_any_instance_of(Flexirest::Connection).to receive(:put).with("/put/1234", %q({"base_data":{"debug":true,"test":"foo"}}), an_instance_of(Hash)).and_return(::FaradayResponseMock.new(OpenStruct.new(body:"{\"result\":true}", response_headers:{})))
|
1481
|
+
GlobalWrappedRootExampleClient.request_body_type :json
|
1482
|
+
GlobalWrappedRootExampleClient.wrapped id:1234, debug:true, test:'foo'
|
1483
|
+
end
|
1484
|
+
|
1485
|
+
it "should encode the body wrapped in a root element in a JSON format if specified" do
|
1486
|
+
expect_any_instance_of(Flexirest::Connection).to receive(:put).with("/put/1234", %q({"class_specific_data":{"debug":true,"test":"foo"}}), an_instance_of(Hash)).and_return(::FaradayResponseMock.new(OpenStruct.new(body:"{\"result\":true}", response_headers:{})))
|
1487
|
+
OverrideGlobalWrappedRootForFileExampleClient.request_body_type :json
|
1488
|
+
OverrideGlobalWrappedRootForFileExampleClient.wrapped id:1234, debug:true, test:'foo'
|
1489
|
+
end
|
1490
|
+
|
1491
|
+
it "should encode the body wrapped in a root element in a JSON format if specified" do
|
1492
|
+
expect_any_instance_of(Flexirest::Connection).to receive(:put).with("/put/1234", %q({"request_specific_data":{"debug":true,"test":"foo"}}), an_instance_of(Hash)).and_return(::FaradayResponseMock.new(OpenStruct.new(body:"{\"result\":true}", response_headers:{})))
|
1493
|
+
OverrideGlobalWrappedRootForRequestExampleClient.request_body_type :json
|
1494
|
+
OverrideGlobalWrappedRootForRequestExampleClient.wrapped id:1234, debug:true, test:'foo'
|
1495
|
+
end
|
1496
|
+
|
1358
1497
|
context "Parameter preparation" do
|
1359
1498
|
method = {url: "http://www.example.com", method: :get}
|
1360
1499
|
object = nil
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flexirest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.10.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Jeffries
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -170,6 +170,20 @@ dependencies:
|
|
170
170
|
- - ">="
|
171
171
|
- !ruby/object:Gem::Version
|
172
172
|
version: '0'
|
173
|
+
- !ruby/object:Gem::Dependency
|
174
|
+
name: rest-client
|
175
|
+
requirement: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
type: :development
|
181
|
+
prerelease: false
|
182
|
+
version_requirements: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - ">="
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
173
187
|
- !ruby/object:Gem::Dependency
|
174
188
|
name: mime-types
|
175
189
|
requirement: !ruby/object:Gem::Requirement
|