flexirest 1.10.1 → 1.10.6

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: 89ef0bd6a927d830d9f98f1e4deeee8d26bf68a203a001f2299785386e1365b9
4
- data.tar.gz: 3fa35c87cb16df1181207a6393682c6ec003346b4789d8bff30bbedbdba0e753
3
+ metadata.gz: eb22808a993f64bfac3dd7e8927b0f9a0847800666962b0182a1e9161cf776fe
4
+ data.tar.gz: ad93e71fdfbb91fc68fc3b7939b7b023399672560e14e976eadb7e1db38bf995
5
5
  SHA512:
6
- metadata.gz: 520a9f3131d517c00180dd15a9fbd8c03372343bde13474457a37db78b321efa54a756878bd5161253ab6646a6e6ffc24171a3f69fca043c14cfb49630cf7ef8
7
- data.tar.gz: 5580fe4c1079b03fb51465550afce7ca4a542051efbd127df0405d113d47955182d88cfc324ff184a36be7eee0d4eaab5fad85efe47f94d5163846aad952df6d
6
+ metadata.gz: 9d82ab1f62bdd4b55a08d0c49584fc251842773ca5a8d986dd980116498a021ff6287092ea83eee429ad1f04dab719cd8b40116b21e432c4ca32d0ffd056549e
7
+ data.tar.gz: 925fbd8da65f0dda69db4629d7fec401944d2846669b272045342a2384671bb45b55aa98905c17328631862b6953ac7ad5675c76980b7896c2a5916c968c365c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.10.6
4
+
5
+ Bugfix:
6
+
7
+ - Flexirest was erroring if Rails.logger was defined but was nil (thanks to Alex Oxte for the PR)
8
+
9
+ ## 1.10.5
10
+
11
+ Enhancement:
12
+
13
+ - Allow skipping of caching for single endpoints
14
+
15
+ ## 1.10.4
16
+
17
+ Enhancement:
18
+
19
+ - Implement support for in-header Basic Auth (thanks to François Ferrandis for the PR)
20
+
21
+ ## 1.10.3
22
+
23
+ Enhancement:
24
+
25
+ - Ignore/wrap root functionality available for all routes, rather than just route specifically (thanks to Sampat Badhe for the PR)
26
+
27
+ ## 1.10.2
28
+
29
+ Bugfix:
30
+
31
+ - 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)
32
+
3
33
  ## 1.10.1
4
34
 
5
35
  Enhancement:
@@ -40,6 +40,30 @@ Or if it's called from a class context:
40
40
  Person.find(id: 1234)
41
41
  ```
42
42
 
43
+ ### Basic authentication method
44
+
45
+ Flexirest provides two methods for HTTP Basic Auth. The default method is `:url`:
46
+
47
+ ```ruby
48
+ class Person < Flexirest::Base
49
+ basic_auth_method :url
50
+ end
51
+ # Includes the credentials in the URL:
52
+ # https://my_username:my_password@example.com/
53
+ ```
54
+
55
+ But you might want to keep the credentials out of your logs and use the `:header` method:
56
+
57
+ ```ruby
58
+ class Person < Flexirest::Base
59
+ basic_auth_method :header
60
+ end
61
+ # Clean URL:
62
+ # https://example.com/
63
+ # with the base64 encoded credentials:
64
+ # Authorization: Basic YXBpOmViNjkzZWMtODI1MmMtZDYzMDEtMDJmZDAtZDBmYjctYzM0ODU=
65
+ ```
66
+
43
67
  ## Api-Auth
44
68
 
45
69
  Using the [Api-Auth](https://github.com/mgomes/api_auth) integration it is very easy to sign requests. Include the Api-Auth gem in your `Gemfile` and then add it to your application. Then simply configure Api-Auth one time in your app and all requests will be signed from then on.
data/docs/caching.md CHANGED
@@ -14,6 +14,14 @@ class Person < Flexirest::Base
14
14
  end
15
15
  ```
16
16
 
17
+ or per request endpoint with:
18
+
19
+ ```ruby
20
+ class Person < Flexirest::Base
21
+ get :all, "/people", skip_caching: true
22
+ end
23
+ ```
24
+
17
25
  If Rails is defined, it will default to using Rails.cache as the cache store, if not, you'll need to configure one with a `ActiveSupport::Cache::Store` compatible object using:
18
26
 
19
27
  ```ruby
@@ -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
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 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
 
data/docs/raw-requests.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # *Flexirest:* Raw requests
2
2
 
3
- 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:
3
+ Sometimes you 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:
4
4
 
5
5
  ```ruby
6
6
  class Person < Flexirest::Base
@@ -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
 
data/flexirest.gemspec CHANGED
@@ -39,7 +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', ">= 1.8.0"
42
+ spec.add_development_dependency 'rest-client'
43
43
 
44
44
  spec.add_runtime_dependency "mime-types"
45
45
  spec.add_runtime_dependency "multi_json"
@@ -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
@@ -109,6 +135,23 @@ module Flexirest
109
135
  @@password = value
110
136
  end
111
137
 
138
+ DEFAULT_BASIC_URL_METHOD = :url
139
+
140
+ def basic_auth_method(value = nil)
141
+ if value.nil? # Used as a getter method
142
+ if @basic_auth_method.nil? && superclass.respond_to?(:basic_auth_method)
143
+ superclass.basic_auth_method
144
+ else
145
+ @basic_auth_method || DEFAULT_BASIC_URL_METHOD
146
+ end
147
+ else # Used as a setter method
148
+ unless [:header, :url].include?(value)
149
+ raise %(Invalid basic_auth_method #{value.inspect}. Valid methods are :url (default) and :header.)
150
+ end
151
+ @basic_auth_method = value
152
+ end
153
+ end
154
+
112
155
  def alias_type(value = nil)
113
156
  @alias_type ||= nil
114
157
  if value.nil?
@@ -285,6 +328,7 @@ module Flexirest
285
328
  @adapter = Faraday.default_adapter
286
329
  @api_auth_access_id = nil
287
330
  @api_auth_secret_key = nil
331
+ @basic_auth_method = :url
288
332
  end
289
333
 
290
334
  private
@@ -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
@@ -17,7 +17,7 @@ module Flexirest
17
17
  end
18
18
 
19
19
  def self.debug(message)
20
- if defined?(Rails) && Rails.respond_to?(:logger)
20
+ if defined?(Rails) && Rails.logger.present?
21
21
  Rails.logger.debug(message)
22
22
  elsif @logfile
23
23
  if @logfile.is_a?(String)
@@ -33,7 +33,7 @@ module Flexirest
33
33
  end
34
34
 
35
35
  def self.info(message)
36
- if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
36
+ if defined?(Rails) && Rails.logger.present?
37
37
  Rails.logger.info(message)
38
38
  elsif @logfile
39
39
  if @logfile.is_a?(String)
@@ -49,7 +49,7 @@ module Flexirest
49
49
  end
50
50
 
51
51
  def self.warn(message)
52
- if defined?(Rails) && Rails.respond_to?(:logger)
52
+ if defined?(Rails) && Rails.logger.present?
53
53
  Rails.logger.warn(message)
54
54
  elsif @logfile
55
55
  if @logfile.is_a?(String)
@@ -65,7 +65,7 @@ module Flexirest
65
65
  end
66
66
 
67
67
  def self.error(message)
68
- if defined?(Rails) && Rails.respond_to?(:logger)
68
+ if defined?(Rails) && Rails.logger.present?
69
69
  Rails.logger.error(message)
70
70
  elsif @logfile
71
71
  if @logfile.is_a?(String)
@@ -32,6 +32,10 @@ module Flexirest
32
32
  !@object.respond_to?(:dirty?)
33
33
  end
34
34
 
35
+ def model_class
36
+ object_is_class? ? @object : @object.class
37
+ end
38
+
35
39
  def class_name
36
40
  if object_is_class?
37
41
  @object.name
@@ -124,6 +128,18 @@ module Flexirest
124
128
  ret
125
129
  end
126
130
 
131
+ def inject_basic_auth_in_url(url)
132
+ url.gsub!(%r{//(.)}, "//#{username}:#{password}@\\1") if !url[%r{//[^/]*:[^/]*@}]
133
+ end
134
+
135
+ def using_basic_auth?
136
+ !!username
137
+ end
138
+
139
+ def basic_auth_digest
140
+ Base64.strict_encode64("#{username}:#{password}")
141
+ end
142
+
127
143
  def request_body_type
128
144
  if @method[:options][:request_body_type]
129
145
  @method[:options][:request_body_type]
@@ -136,6 +152,30 @@ module Flexirest
136
152
  end
137
153
  end
138
154
 
155
+ def ignore_root
156
+ if @method[:options][:ignore_root]
157
+ @method[:options][:ignore_root]
158
+ elsif @object.nil?
159
+ nil
160
+ elsif object_is_class?
161
+ @object.ignore_root
162
+ else
163
+ @object.class.ignore_root
164
+ end
165
+ end
166
+
167
+ def wrap_root
168
+ if @method[:options][:wrap_root]
169
+ @method[:options][:wrap_root]
170
+ elsif @object.nil?
171
+ nil
172
+ elsif object_is_class?
173
+ @object.wrap_root
174
+ else
175
+ @object.class.wrap_root
176
+ end
177
+ end
178
+
139
179
  def verbose?
140
180
  if object_is_class?
141
181
  @object.verbose
@@ -272,7 +312,7 @@ module Flexirest
272
312
 
273
313
  result = handle_response(response_env, cached)
274
314
  @response_delegate.__setobj__(result)
275
- original_object_class.write_cached_response(self, response_env, result)
315
+ original_object_class.write_cached_response(self, response_env, result) unless @method[:options][:skip_caching]
276
316
  end
277
317
 
278
318
  # If this was not a parallel request just return the original result
@@ -438,8 +478,8 @@ module Flexirest
438
478
  @post_params
439
479
  else
440
480
  p = (params || @post_params || {})
441
- if @method[:options][:wrap_root].present?
442
- p = {@method[:options][:wrap_root] => p}
481
+ if wrap_root.present?
482
+ p = {wrap_root => p}
443
483
  end
444
484
  p.to_query
445
485
  end
@@ -452,8 +492,8 @@ module Flexirest
452
492
  @post_params
453
493
  else
454
494
  p = (params || @post_params || {})
455
- if @method[:options][:wrap_root].present?
456
- p = {@method[:options][:wrap_root] => p}
495
+ if wrap_root.present?
496
+ p = {wrap_root => p}
457
497
  end
458
498
  data, mp_headers = Flexirest::Multipart::Post.prepare_query(p)
459
499
  mp_headers.each do |k,v|
@@ -467,8 +507,8 @@ module Flexirest
467
507
  elsif @post_params.is_a?(String)
468
508
  @post_params
469
509
  else
470
- if @method[:options][:wrap_root].present?
471
- {@method[:options][:wrap_root] => (params || @post_params || {})}.to_json
510
+ if wrap_root.present?
511
+ {wrap_root => (params || @post_params || {})}.to_json
472
512
  else
473
513
  (params || @post_params || {}).to_json
474
514
  end
@@ -483,7 +523,7 @@ module Flexirest
483
523
 
484
524
  def do_request(etag)
485
525
  http_headers = {}
486
- http_headers["If-None-Match"] = etag if etag
526
+ http_headers["If-None-Match"] = etag if etag && !@method[:options][:skip_caching]
487
527
  http_headers["Accept"] = "application/hal+json, application/json;q=0.5"
488
528
  headers.each do |key,value|
489
529
  value = value.join(",") if value.is_a?(Array)
@@ -503,7 +543,9 @@ module Flexirest
503
543
  else
504
544
  _, @base_url, @url = parts
505
545
  end
506
- base_url.gsub!(%r{//(.)}, "//#{username}:#{password}@\\1") if username && !base_url[%r{//[^/]*:[^/]*@}]
546
+ if using_basic_auth? && model_class.basic_auth_method == :url
547
+ inject_basic_auth_in_url(base_url)
548
+ end
507
549
  connection = Flexirest::ConnectionManager.get_connection(base_url)
508
550
  end
509
551
  else
@@ -516,7 +558,9 @@ module Flexirest
516
558
  else
517
559
  base_url = parts[0]
518
560
  end
519
- base_url.gsub!(%r{//(.)}, "//#{username}:#{password}@\\1") if username && !base_url[%r{//[^/]*:[^/]*@}]
561
+ if using_basic_auth? && model_class.basic_auth_method == :url
562
+ inject_basic_auth_in_url(base_url)
563
+ end
520
564
  connection = Flexirest::ConnectionManager.get_connection(base_url)
521
565
  end
522
566
  if @method[:options][:direct]
@@ -542,6 +586,8 @@ module Flexirest
542
586
  :api_auth_secret_key => api_auth_secret_key,
543
587
  :api_auth_options => api_auth_options
544
588
  }
589
+ elsif using_basic_auth? && model_class.basic_auth_method == :header
590
+ http_headers["Authorization"] = "Basic #{basic_auth_digest}"
545
591
  end
546
592
  if @method[:options][:timeout]
547
593
  request_options[:timeout] = @method[:options][:timeout]
@@ -601,7 +647,7 @@ module Flexirest
601
647
  else
602
648
  Flexirest::Logger.debug " \033[1;4;32m#{Flexirest.name}\033[0m #{@instrumentation_name} - Response received #{@response.body.size} bytes"
603
649
  end
604
- result = generate_new_object(ignore_root: @method[:options][:ignore_root], ignore_xml_root: @method[:options][:ignore_xml_root])
650
+ result = generate_new_object(ignore_root: ignore_root, ignore_xml_root: @method[:options][:ignore_xml_root])
605
651
  # TODO: Cleanup when ignore_xml_root is removed
606
652
  else
607
653
  raise ResponseParseException.new(status:status, body:@response.body, headers: @response.headers)
@@ -814,15 +860,15 @@ module Flexirest
814
860
  body = JsonAPIProxy::Response.parse(body, @object)
815
861
  end
816
862
 
817
- if options[:ignore_root]
818
- [options[:ignore_root]].flatten.each do |key|
863
+ if ignore_root
864
+ [ignore_root].flatten.each do |key|
819
865
  body = body[key.to_s]
820
866
  end
821
867
  end
822
868
  elsif is_xml_response?
823
869
  body = @response.body.blank? ? {} : Crack::XML.parse(@response.body)
824
- if options[:ignore_root]
825
- [options[:ignore_root]].flatten.each do |key|
870
+ if ignore_root
871
+ [ignore_root].flatten.each do |key|
826
872
  body = body[key.to_s]
827
873
  end
828
874
  elsif options[:ignore_xml_root]
@@ -844,7 +890,7 @@ module Flexirest
844
890
  result = new_object(body, @overridden_name)
845
891
  result._status = @response.status
846
892
  result._headers = @response.response_headers
847
- result._etag = @response.response_headers['ETag']
893
+ result._etag = @response.response_headers['ETag'] unless @method[:options][:skip_caching]
848
894
  if !object_is_class? && options[:mutable] != false
849
895
  @object._copy_from(result)
850
896
  @object._clean!
@@ -1,3 +1,3 @@
1
1
  module Flexirest
2
- VERSION = "1.10.1"
2
+ VERSION = "1.10.6"
3
3
  end
@@ -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')
@@ -41,6 +41,7 @@ describe Flexirest::Request do
41
41
  get :find, "/:id", required: [:id]
42
42
  get :find_cat, "/:id/cat"
43
43
  get :fruits, "/fruits"
44
+ get :uncached, "/uncached", skip_caching: true
44
45
  get :change, "/change"
45
46
  get :plain, "/plain/:id", plain: true
46
47
  post :create, "/create", rubify_names: true
@@ -81,6 +82,26 @@ describe Flexirest::Request do
81
82
  get :all, "/"
82
83
  end
83
84
 
85
+ class AuthenticatedBasicHeaderExampleClient < Flexirest::Base
86
+ base_url "http://www.example.com"
87
+ username "john"
88
+ password "smith"
89
+ basic_auth_method :header
90
+ get :all, "/"
91
+ end
92
+
93
+ class AuthenticatedBasicHeaderExampleClientChildClass < AuthenticatedBasicHeaderExampleClient
94
+ get :child_method, "/"
95
+ end
96
+
97
+ class AuthenticatedBasicUrlExampleClient < Flexirest::Base
98
+ base_url "http://www.example.com"
99
+ username "john"
100
+ password "smith"
101
+ basic_auth_method :url
102
+ get :all, "/"
103
+ end
104
+
84
105
  class AuthenticatedProcExampleClient < Flexirest::Base
85
106
  base_url "http://www.example.com"
86
107
  username Proc.new { |obj| obj ? "bill-#{obj.id}" : "bill" }
@@ -199,6 +220,89 @@ describe Flexirest::Request do
199
220
  }
200
221
  end
201
222
 
223
+ class LocalIgnoredRootExampleClient < ExampleClient
224
+ ignore_root "feed"
225
+
226
+ get :root, "/root", fake: %Q{
227
+ {
228
+ "feed": {
229
+ "title": "Example Feed"
230
+ }
231
+ }
232
+ }
233
+ end
234
+
235
+ class LocalIgnoredMultiLevelRootExampleClient < ExampleClient
236
+ ignore_root [:response, "data", "object"]
237
+
238
+ get :multi_level_root, "/multi-level-root", fake: %Q{
239
+ {
240
+ "response": {
241
+ "data": {
242
+ "object": {
243
+ "title": "Example Multi Level Feed"
244
+ }
245
+ }
246
+ }
247
+ }
248
+ }
249
+ end
250
+
251
+ class BaseIgnoredRootExampleClient < Flexirest::Base
252
+ base_url "http://www.example.com"
253
+ ignore_root "feed"
254
+ end
255
+
256
+ class GlobalIgnoredRootExampleClient < BaseIgnoredRootExampleClient
257
+ get :root, "/root", fake: %Q{
258
+ {
259
+ "feed": {
260
+ "title": "Example Feed"
261
+ }
262
+ }
263
+ }
264
+ end
265
+
266
+ class OverrideGlobalIgnoredRootForFileExampleClient < BaseIgnoredRootExampleClient
267
+ ignore_root "data"
268
+
269
+ get :root, "/root", fake: %Q{
270
+ {
271
+ "data": {
272
+ "title": "Example Feed"
273
+ }
274
+ }
275
+ }
276
+ end
277
+
278
+ class OverrideGlobalIgnoredRootForRequestExampleClient < BaseIgnoredRootExampleClient
279
+ get :root, "/root", ignore_root: "data", fake: %Q{
280
+ {
281
+ "data": {
282
+ "title": "Example Feed"
283
+ }
284
+ }
285
+ }
286
+ end
287
+
288
+ class BaseWrappedRootExampleClient < Flexirest::Base
289
+ base_url "http://www.example.com"
290
+ wrap_root "base_data"
291
+ end
292
+
293
+ class GlobalWrappedRootExampleClient < BaseWrappedRootExampleClient
294
+ put :wrapped, "/put/:id"
295
+ end
296
+
297
+ class OverrideGlobalWrappedRootForFileExampleClient < BaseWrappedRootExampleClient
298
+ wrap_root "class_specific_data"
299
+ put :wrapped, "/put/:id"
300
+ end
301
+
302
+ class OverrideGlobalWrappedRootForRequestExampleClient < BaseWrappedRootExampleClient
303
+ put :wrapped, "/put/:id", wrap_root: "request_specific_data"
304
+ end
305
+
202
306
  class WhitelistedDateClient < Flexirest::Base
203
307
  base_url "http://www.example.com"
204
308
  put :conversion, "/put/:id"
@@ -231,13 +335,53 @@ describe Flexirest::Request do
231
335
  expect(servers.uniq.count).to eq(2)
232
336
  end
233
337
 
234
- it "should get an HTTP connection with authentication when called" do
338
+ it "should use the URL method for Basic HTTP Auth when no basic_auth_method is set" do
339
+ mocked_response = ::FaradayResponseMock.new(OpenStruct.new(body:'{"result":true}', response_headers:{}))
340
+
235
341
  connection = double(Flexirest::Connection).as_null_object
236
342
  expect(Flexirest::ConnectionManager).to receive(:get_connection).with("http://john:smith@www.example.com").and_return(connection)
237
- expect(connection).to receive(:get).with("/", an_instance_of(Hash)).and_return(::FaradayResponseMock.new(OpenStruct.new(body:'{"result":true}', response_headers:{})))
343
+ expect(connection).to receive(:get).with("/", an_instance_of(Hash)).and_return(mocked_response)
238
344
  AuthenticatedExampleClient.all
239
345
  end
240
346
 
347
+ it "should use the headers method for Basic auth when basic_auth_method is set to :header" do
348
+ mocked_response = ::FaradayResponseMock.new(OpenStruct.new(body:'{"result":true}', response_headers:{}))
349
+ headers_including_auth = hash_including({ "Authorization" => "Basic am9objpzbWl0aA==" })
350
+
351
+ connection = double(Flexirest::Connection).as_null_object
352
+ expect(Flexirest::ConnectionManager).to receive(:get_connection).with("http://www.example.com").and_return(connection)
353
+ expect(connection).to receive(:get).with("/", hash_including(headers: headers_including_auth)).and_return(mocked_response)
354
+ AuthenticatedBasicHeaderExampleClient.all
355
+ end
356
+
357
+ it "should raise an error if Basic HTTP method is not :header or :url" do
358
+ expect do
359
+ AuthenticatedExampleClient.class_eval do
360
+ basic_auth_method :some_invalid_value
361
+ end
362
+ end.to raise_error(RuntimeError, "Invalid basic_auth_method :some_invalid_value. Valid methods are :url (default) and :header.")
363
+ end
364
+
365
+ it "should use the setting set on the parent class" do
366
+ mocked_response = ::FaradayResponseMock.new(OpenStruct.new(body:'{"result":true}', response_headers:{}))
367
+ headers_including_auth = hash_including({ "Authorization" => "Basic am9objpzbWl0aA==" })
368
+
369
+ connection = double(Flexirest::Connection).as_null_object
370
+ expect(Flexirest::ConnectionManager).to receive(:get_connection).with("http://www.example.com").and_return(connection)
371
+ expect(connection).to receive(:get).with("/", hash_including(headers: headers_including_auth)).and_return(mocked_response)
372
+ AuthenticatedBasicHeaderExampleClientChildClass.child_method
373
+ end
374
+
375
+ it "should use the URL method for Basic auth when basic_auth_method is set to :url (and not include Authorization header)" do
376
+ mocked_response = ::FaradayResponseMock.new(OpenStruct.new(body:'{"result":true}', response_headers:{}))
377
+ headers_not_including_auth = hash_excluding("Authorization")
378
+
379
+ connection = double(Flexirest::Connection).as_null_object
380
+ expect(Flexirest::ConnectionManager).to receive(:get_connection).with("http://john:smith@www.example.com").and_return(connection)
381
+ expect(connection).to receive(:get).with("/", headers: headers_not_including_auth).and_return(mocked_response)
382
+ AuthenticatedBasicUrlExampleClient.all
383
+ end
384
+
241
385
  it "should get an HTTP connection with basic authentication using procs when called in a class context" do
242
386
  connection = double(Flexirest::Connection).as_null_object
243
387
  expect(Flexirest::ConnectionManager).to receive(:get_connection).with("http://bill:jones@www.example.com").and_return(connection)
@@ -854,6 +998,35 @@ describe Flexirest::Request do
854
998
  expect(object._etag).to eq("123456")
855
999
  end
856
1000
 
1001
+ it "shouldn't expose the etag header if skip_caching is enabled" do
1002
+ response = ::FaradayResponseMock.new(OpenStruct.new(body: "{}", response_headers: {"ETag" => "123456"}, status: 200))
1003
+ expect_any_instance_of(Flexirest::Connection).to receive(:get).with("/uncached", an_instance_of(Hash)).and_return(response)
1004
+ object = ExampleClient.uncached
1005
+ expect(object._etag).to_not eq("123456")
1006
+ end
1007
+
1008
+ it "shouldn't send the etag header if skip_caching is enabled" do
1009
+ cached_response = Flexirest::CachedResponse.new(status:200, result:"", response_headers: {})
1010
+ cached_response.etag = "123456"
1011
+ expect(ExampleClient).to receive(:read_cached_response).and_return(cached_response)
1012
+
1013
+ response = ::FaradayResponseMock.new(OpenStruct.new(body: "{}", response_headers: {"ETag" => "123456"}, status: 200))
1014
+ expect_any_instance_of(Flexirest::Connection).to receive(:get).with("/uncached", {
1015
+ api_auth: {
1016
+ api_auth_access_id: "id123",
1017
+ api_auth_options: {},
1018
+ api_auth_secret_key: "secret123"
1019
+ },
1020
+ headers: {
1021
+ "Accept"=>"application/hal+json, application/json;q=0.5",
1022
+ "Content-Type"=>"application/x-www-form-urlencoded; charset=utf-8"
1023
+ }
1024
+ }).and_return(response)
1025
+
1026
+ expect(ExampleClient).to_not receive(:write_cached_response)
1027
+ object = ExampleClient.uncached
1028
+ end
1029
+
857
1030
  it "should expose all headers" do
858
1031
  response = ::FaradayResponseMock.new(OpenStruct.new(body: "{}", response_headers: {"X-Test-Header" => "true"}, status: 200))
859
1032
  expect_any_instance_of(Flexirest::Connection).to receive(:get).with("/123", an_instance_of(Hash)).and_return(response)
@@ -1355,6 +1528,62 @@ describe Flexirest::Request do
1355
1528
  expect(IgnoredMultiLevelRootExampleClient.multi_level_root.title).to eq("Example Multi Level Feed")
1356
1529
  end
1357
1530
 
1531
+ it "should ignore a specified root element" do
1532
+ expect(LocalIgnoredRootExampleClient.root.title).to eq("Example Feed")
1533
+ end
1534
+
1535
+ it "should ignore a specified multi-level root element" do
1536
+ expect(LocalIgnoredMultiLevelRootExampleClient.multi_level_root.title).to eq("Example Multi Level Feed")
1537
+ end
1538
+
1539
+ it "should ignore a specified root element" do
1540
+ expect(GlobalIgnoredRootExampleClient.root.title).to eq("Example Feed")
1541
+ end
1542
+
1543
+ it "should ignore a specified root element" do
1544
+ expect(OverrideGlobalIgnoredRootForFileExampleClient.root.title).to eq("Example Feed")
1545
+ end
1546
+
1547
+ it "should ignore a specified root element" do
1548
+ expect(OverrideGlobalIgnoredRootForRequestExampleClient.root.title).to eq("Example Feed")
1549
+ end
1550
+
1551
+ it "should wrap elements if specified, in form-encoded format" do
1552
+ 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:{})))
1553
+ GlobalWrappedRootExampleClient.request_body_type :form_encoded
1554
+ GlobalWrappedRootExampleClient.wrapped id:1234, debug:true, test:'foo'
1555
+ end
1556
+
1557
+ it "should wrap elements if specified, in form-encoded format" do
1558
+ 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:{})))
1559
+ OverrideGlobalWrappedRootForFileExampleClient.request_body_type :form_encoded
1560
+ OverrideGlobalWrappedRootForFileExampleClient.wrapped id:1234, debug:true, test:'foo'
1561
+ end
1562
+
1563
+ it "should wrap elements if specified, in form-encoded format" do
1564
+ 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:{})))
1565
+ OverrideGlobalWrappedRootForRequestExampleClient.request_body_type :form_encoded
1566
+ OverrideGlobalWrappedRootForRequestExampleClient.wrapped id:1234, debug:true, test:'foo'
1567
+ end
1568
+
1569
+ it "should encode the body wrapped in a root element in a JSON format if specified" do
1570
+ 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:{})))
1571
+ GlobalWrappedRootExampleClient.request_body_type :json
1572
+ GlobalWrappedRootExampleClient.wrapped id:1234, debug:true, test:'foo'
1573
+ end
1574
+
1575
+ it "should encode the body wrapped in a root element in a JSON format if specified" do
1576
+ 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:{})))
1577
+ OverrideGlobalWrappedRootForFileExampleClient.request_body_type :json
1578
+ OverrideGlobalWrappedRootForFileExampleClient.wrapped id:1234, debug:true, test:'foo'
1579
+ end
1580
+
1581
+ it "should encode the body wrapped in a root element in a JSON format if specified" do
1582
+ 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:{})))
1583
+ OverrideGlobalWrappedRootForRequestExampleClient.request_body_type :json
1584
+ OverrideGlobalWrappedRootForRequestExampleClient.wrapped id:1234, debug:true, test:'foo'
1585
+ end
1586
+
1358
1587
  context "Parameter preparation" do
1359
1588
  method = {url: "http://www.example.com", method: :get}
1360
1589
  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.10.1
4
+ version: 1.10.6
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-10-12 00:00:00.000000000 Z
11
+ date: 2021-06-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -176,14 +176,14 @@ dependencies:
176
176
  requirements:
177
177
  - - ">="
178
178
  - !ruby/object:Gem::Version
179
- version: 1.8.0
179
+ version: '0'
180
180
  type: :development
181
181
  prerelease: false
182
182
  version_requirements: !ruby/object:Gem::Requirement
183
183
  requirements:
184
184
  - - ">="
185
185
  - !ruby/object:Gem::Version
186
- version: 1.8.0
186
+ version: '0'
187
187
  - !ruby/object:Gem::Dependency
188
188
  name: mime-types
189
189
  requirement: !ruby/object:Gem::Requirement