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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50fe9393bbb548c2052ca5f971b7c406a9daf742d398fcd0681a917e03dd6247
4
- data.tar.gz: b1dc3e1b1c97626ed7956f7a7ba31f4bd424d9a1a539645de8bbd456348eb814
3
+ metadata.gz: 53b34d9600101b9d80a61559054a3599242a7739b12845df02bde410c5858d94
4
+ data.tar.gz: e7398bcc97a8a0f89fb3380110e126e482d5fc1e14a973dd9e34208f28634814
5
5
  SHA512:
6
- metadata.gz: 23ca59d17ad6df1bf15f8dc40bf4fd826132016d82de50a40824ac9d7d98858e92ab621bc8d0681bb61db950ae50853b3017bae86b8ae96ecccc1270a757c018
7
- data.tar.gz: 60bae684f60df01e8419f97d870bb3412192c6e7c85ecff1abf578d6210bc8d336781e1f1bd1d4f05753b1cccd3a44923049dd43a76f55f5b3ee233807b75fbb
6
+ metadata.gz: 32014dfe143032f040ae140cdcf562f05832f74d0b05e692b8f866d4636363b6f7ffcbe0a4de0e7a895c8b1722aef0f6736f34c130916dc5c02869caa198434a
7
+ data.tar.gz: a7f3cb00fd3451c4f06ef32bf6e148f90d4046c1cce7526a8bfe6c247e2d65dcd68a0d924f85683ef052929432ddf341768578a7591b37db8b7729ed96053c80
@@ -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 retured 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):
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
@@ -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 with parameters: include=images
21
+ # Makes a call to: /articles?include=images
19
22
  Article.includes(:images).all
20
23
 
21
- # For nested resources, the include parameter becomes: include=images.tags,images.photographer
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
- For POST and PATCH requests, the proxy formats a JSON API compliant 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`:
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
- NB: Updating relationships is not yet supported.
46
+
47
+ ## Notes
48
+
49
+ Updating relationships is not yet supported.
38
50
 
39
51
 
40
52
  -----
@@ -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 normal Flexirest object, implementing iteration, HAL loading, etc.
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
  ```
@@ -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 alterered, or a new object to replace it with)
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
 
@@ -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 paramaters you'll need to specify them. For example:
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"}})
@@ -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
- post :list, "/feed", ignore_root: "feed"
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
- This also works if you'd want to remove a tree of root nodes:
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 like this:
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
- post :list, "/feed", wrap_root: "feed"
34
- end
58
+ wrap_root: "feed"
35
59
 
36
- Feed.list(id: 1)
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
 
@@ -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
@@ -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
@@ -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 @method[:options][:wrap_root].present?
442
- p = {@method[:options][:wrap_root] => 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 @method[:options][:wrap_root].present?
456
- p = {@method[:options][:wrap_root] => 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 @method[:options][:wrap_root].present?
471
- {@method[:options][:wrap_root] => (params || @post_params || {})}.to_json
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: @method[:options][:ignore_root], ignore_xml_root: @method[:options][:ignore_xml_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 options[:ignore_root]
805
- [options[:ignore_root]].flatten.each do |key|
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 options[:ignore_root]
812
- [options[:ignore_root]].flatten.each do |key|
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 &Proc.new
71
+ def delete_if(&block)
72
+ @items = @items.delete_if &block
71
73
  self
72
74
  end
73
75
 
@@ -1,3 +1,3 @@
1
1
  module Flexirest
2
- VERSION = "1.9.17"
2
+ VERSION = "1.10.3"
3
3
  end
@@ -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"
@@ -303,7 +303,7 @@ describe 'JSON API' do
303
303
  let(:tags) { JsonAPIExample::Tag }
304
304
  let(:author) { JsonAPIExample::Author }
305
305
 
306
- context 'responses' do
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
- context 'attributes' do
373
+ describe 'attributes' do
374
374
  it 'should return the attributes as part of the data instance' do
375
- expect(subject.find(1).item).to_not be_nil
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).to_not be_nil
379
+ expect(subject.includes(:author).find_single_author(1).author.item).to eq("item two")
380
380
  end
381
381
  end
382
382
 
383
- context 'associations' do
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
- context 'client' do
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')
@@ -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::HTTPServerException)
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::HTTPServerException with a body of: \{"first_name":"John", "id":1234\}})
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::HTTPServerException)
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.9.17
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: 2020-08-24 00:00:00.000000000 Z
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