flexirest 1.6.5 → 1.6.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +92 -1243
  4. data/docs/associations.md +181 -0
  5. data/docs/authentication.md +76 -0
  6. data/docs/automatic-conversion-of-fields-to-datedatetime.md +34 -0
  7. data/docs/basic-usage.md +103 -0
  8. data/docs/body-types.md +33 -0
  9. data/docs/caching.md +26 -0
  10. data/docs/combined-example.md +72 -0
  11. data/docs/debugging.md +32 -0
  12. data/docs/default-parameters.md +37 -0
  13. data/docs/faking-calls.md +22 -0
  14. data/docs/faraday-configuration.md +28 -0
  15. data/docs/filtering-result-lists.md +16 -0
  16. data/docs/httpparse-error-handling.md +17 -0
  17. data/{CONTRIBUTING.md → docs/internals.md} +4 -6
  18. data/docs/json-api.md +42 -0
  19. data/docs/lazy-loading.md +31 -0
  20. data/{Migrating-from-ActiveRestClient.md → docs/migrating-from-activerestclient.md} +2 -2
  21. data/docs/parallel-requests.md +28 -0
  22. data/docs/per-request-parameter-encoding.md +32 -0
  23. data/docs/per-request-timeouts.md +13 -0
  24. data/docs/plain-requests.md +30 -0
  25. data/docs/proxying-apis.md +86 -0
  26. data/docs/raw-requests.md +38 -0
  27. data/docs/required-parameters.md +17 -0
  28. data/docs/root-elements.md +34 -0
  29. data/{Ruby-on-Rails-Integration.md → docs/ruby-on-rails-integration.md} +16 -12
  30. data/docs/{Flexirest Internals.graffle → source/Flexirest Internals.graffle} +0 -0
  31. data/docs/{Flexirest Logo.sketch → source/Flexirest Logo.sketch} +0 -0
  32. data/docs/translating-apis.md +31 -0
  33. data/docs/updating-only-changed-dirty-attributes.md +37 -0
  34. data/docs/using-callbacks.md +114 -0
  35. data/docs/validation.md +89 -0
  36. data/docs/xml-responses.md +58 -0
  37. data/lib/flexirest/configuration.rb +36 -20
  38. data/lib/flexirest/request.rb +12 -4
  39. data/lib/flexirest/version.rb +1 -1
  40. data/spec/lib/request_spec.rb +28 -0
  41. metadata +35 -7
@@ -0,0 +1,114 @@
1
+ # *Flexirest:* Using callbacks
2
+
3
+ You can use callbacks to alter get/post parameters, the URL or set the post body (doing so overrides normal parameter insertion in to the body) before a request or to adjust the response after a request. This can either be a block or a named method (like ActionController's `before_callback`/`before_action` methods).
4
+
5
+ The callback is passed the name of the method (e.g. `:save`) and an object (a request object for `before_request` and a response object for `after_request`). The request object has four public attributes `post_params` (a Hash of the POST parameters), `get_params` (a Hash of the GET parameters), `headers` and `url` (a `String` containing the full URL without GET parameters appended).
6
+
7
+ ```ruby
8
+ require 'secure_random'
9
+
10
+ class Person < Flexirest::Base
11
+ before_request do |name, request|
12
+ if request.post? || name == :save
13
+ id = request.post_params.delete(:id)
14
+ request.get_params[:id] = id
15
+ end
16
+ end
17
+
18
+ before_request :replace_token_in_url
19
+
20
+ before_request :add_authentication_details
21
+
22
+ before_request :replace_body
23
+
24
+ before_request :override_default_content_type
25
+
26
+ private
27
+
28
+ def replace_token_in_url(name, request)
29
+ request.url.gsub!("#token", SecureRandom.hex)
30
+ end
31
+
32
+ def add_authentication_details(name, request)
33
+ request.headers["X-Custom-Authentication-Token"] = ENV["AUTH_TOKEN"]
34
+ end
35
+
36
+ def replace_body(name, request)
37
+ if name == :create
38
+ request.body = request.post_params.to_json
39
+ end
40
+ end
41
+
42
+ def override_default_content_type(name, request)
43
+ if name == :save
44
+ request.headers["Content-Type"] = "application/json"
45
+ end
46
+ end
47
+ end
48
+ ```
49
+
50
+ If you need to, you can create a custom parent class with a `before_request` callback and all children will inherit this callback.
51
+
52
+ ```ruby
53
+ class MyProject::Base < Flexirest::Base
54
+ before_request do |name, request|
55
+ request.get_params[:api_key] = "1234567890-1234567890"
56
+ end
57
+ end
58
+
59
+ class Person < MyProject::Base
60
+ # No need to declare a before_request for :api_key, already defined by the parent
61
+ end
62
+ ```
63
+
64
+ After callbacks work in exactly the same way:
65
+
66
+ ```ruby
67
+ class Person < Flexirest::Base
68
+ get :all, "/people"
69
+
70
+ after_request :fix_empty_content
71
+ after_request :cache_all_people
72
+
73
+ private
74
+
75
+ def fix_empty_content(name, response)
76
+ if response.status == 204 && response.body.blank?
77
+ response.body = '{"empty": true}'
78
+ end
79
+ end
80
+
81
+ def cache_all_people(name, response)
82
+ if name == :all
83
+ response.response_headers["Expires"] = 1.hour.from_now.iso8601
84
+ end
85
+ end
86
+ end
87
+ ```
88
+
89
+ **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
+ 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 returning `:retry` from the callback.
92
+
93
+ ```ruby
94
+ class Person < Flexirest::Base
95
+ get :all, "/people"
96
+
97
+ after_request :fix_invalid_request
98
+
99
+ private
100
+
101
+ def fix_invalid_request(name, response)
102
+ if response.status == 401
103
+ # Do something to fix the state of caches/variables used in the
104
+ # before_request, etc
105
+ return :retry
106
+ end
107
+ end
108
+ end
109
+ ```
110
+
111
+
112
+ -----
113
+
114
+ [< Caching](caching.md) | [Lazy loading >](lazy-loading.md)
@@ -0,0 +1,89 @@
1
+ # *Flexirest:* Validation
2
+
3
+ You can create validations on your objects just like Rails' built in ActiveModel validations. For example:
4
+
5
+ ```ruby
6
+ class Person < Flexirest::Base
7
+ validates :first_name, presence: true #ensures that the value is present and not blank
8
+ validates :last_name, existence: true #ensures that the value is non-nil only
9
+ validates :password, length: {within:6..12}, message: "Invalid password length, must be 6-12 characters"
10
+ validates :post_code, length: {minimum:6, maximum:8}
11
+ validates :salary, numericality: true, minimum: 20_000, maximum: 50_000
12
+ validates :age, numericality: { minumum: 18, maximum: 65 }
13
+ validates :suffix, inclusion: { in: %w{Dr. Mr. Mrs. Ms.}}
14
+
15
+ validates :first_name do |object, name, value|
16
+ object._errors[name] << "must be over 4 chars long" if value.length <= 4
17
+ end
18
+
19
+ get :index, '/'
20
+ end
21
+ ```
22
+
23
+ Note: the block based validation is responsible for adding errors to `object._errors[name]` (and this will automatically be ready for `<<` inserting into).
24
+
25
+ Validations are run when calling `valid?` or when calling any API on an instance (and then only if it is `valid?` will the API go on to be called).
26
+
27
+ `full_error_messages` returns an array of attributes with their associated error messages, i.e. `["age must be at least 18"]`. Custom messages can be specified by passing a `:message` option to `validates`. This differs slightly from ActiveRecord in that it's an option to `validates` itself, not a part of a final hash of other options. This is because the author doesn't like the ActiveRecord format (but will accept pull requests that make both syntaxes valid). To make this clearer, an example may help:
28
+
29
+ ```ruby
30
+ # ActiveRecord
31
+ validates :name, presence: { message: "must be given please" }
32
+
33
+ # Flexirest
34
+ validates :name, :presence, message: "must be given please"
35
+ ```
36
+
37
+ ## Permitting nil values
38
+
39
+ The default behavior for `:length`, `:numericality` and `:inclusion` validators is to fail when a `nil` value is encountered. You can prevent `nil` attribute values from triggering validation errors for attributes that may permit `nil` by adding the `:allow_nil => true` option. Adding this option with a `true` value to `:length`, `:numericality` and `:inclusion` validators will permit `nil` values and not trigger errors. Some examples are:
40
+
41
+ ```ruby
42
+ class Person < Flexirest::Base
43
+ validates :first_name, presence: true
44
+ validates :middle_name, length: { minimum: 2, maximum: 30 }, allow_nil: true
45
+ validates :last_name, existence: true
46
+ validates :nick_name, length: { minimum: 2, maximum: 30 }
47
+ validates :alias, length: { minimum: 2, maximum: 30 }, allow_nil: false
48
+ validates :password, length: { within: 6..12 }
49
+ validates :post_code, length: { minimum: 6, maximum: 8 }
50
+ validates :salary, numericality: true, minimum: 20_000, maximum: 50_000
51
+ validates :age, numericality: { minimum: 18, maximum: 65 }
52
+ validates :suffix, inclusion: { in: %w{Dr. Mr. Mrs. Ms.}}
53
+ validates :golf_score, numericality: true, allow_nil: true
54
+ validates :retirement_age, numericality: { minimum: 65 }, allow_nil: true
55
+ validates :cars_owned, numericality: true
56
+ validates :houses_owned, numericality: true, allow_nil: false
57
+ validates :favorite_authors, inclusion: { in: ["George S. Klason", "Robert T. Kiyosaki", "Lee Child"] }, allow_nil: true
58
+ validates :favorite_artists, inclusion: { in: ["Claude Monet", "Vincent Van Gogh", "Andy Warhol"] }
59
+ validates :favorite_composers, inclusion: { in: ["Mozart", "Bach", "Pachelbel", "Beethoven"] }, allow_nil: false
60
+ end
61
+ ```
62
+
63
+ In the example above, the following results would occur when calling `valid?` on an instance where all attributes have `nil` values:
64
+
65
+ - `:first_name` must be present
66
+ - `:last_name` must be not be nil
67
+ - `:nick_name` must be not be nil
68
+ - `:alias` must not be nil
69
+ - `:password` must not be nil
70
+ - `:post_code` must not be nil
71
+ - `:salary` must not be nil
72
+ - `:age` must not be nil
73
+ - `:suffix` must not be nil
74
+ - `:cars_owned` must not be nil
75
+ - `:houses_owned` must not be nil
76
+ - `:favorite_artists` must not be nil
77
+ - `:favorite_composers` must not be nil
78
+
79
+ The following attributes will pass validation since they explicitly `allow_nil`:
80
+
81
+ - `:middle_name`
82
+ - `:golf_score`
83
+ - `:retirement_age`
84
+ - `:favorite_authors`
85
+
86
+
87
+ -----
88
+
89
+ [< HTTP/parse error handling](httpparse-error-handling.md) | [Filtering result lists >](filtering-result-lists.md)
@@ -0,0 +1,58 @@
1
+ # *Flexirest:* XML Responses
2
+
3
+ Flexirest uses `Crack` to allow parsing of XML responses. For example, given an XML response of (with a content type of `application/xml` or `text/xml`):
4
+
5
+ ```xml
6
+ <?xml version="1.0" encoding="utf-8"?>
7
+ <feed xmlns="http://www.w3.org/2005/Atom">
8
+
9
+ <title>Example Feed</title>
10
+ <link href="http://example.org/"/>
11
+ <updated>2003-12-13T18:30:02Z</updated>
12
+ <author>
13
+ <name>John Doe</name>
14
+ </author>
15
+ <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
16
+
17
+ <entry>
18
+ <title>Atom-Powered Robots Run Amok</title>
19
+ <link href="http://example.org/2003/12/13/atom03"/>
20
+ <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
21
+ <updated>2003-12-13T18:30:02Z</updated>
22
+ <summary>Some text.</summary>
23
+ </entry>
24
+
25
+ <entry>
26
+ <title>Something else cool happened</title>
27
+ <link href="http://example.org/2015/08/11/andyjeffries"/>
28
+ <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6b</id>
29
+ <updated>2015-08-11T18:30:02Z</updated>
30
+ <summary>Some other text.</summary>
31
+ </entry>
32
+
33
+ </feed>
34
+ ```
35
+
36
+ You can use:
37
+
38
+ ```ruby
39
+ class Feed < Flexirest::Base
40
+ base_url "http://www.example.com/v1/"
41
+ get :atom, "/atom"
42
+ end
43
+
44
+ @atom = Feed.atom
45
+
46
+ puts @atom.feed.title
47
+ puts @atom.feed.link.href
48
+ @atom.feed.entry.each do |entry|
49
+ puts "#{entry.title} -> #{entry.link.href}"
50
+ end
51
+ ```
52
+
53
+ For testing purposes, if you are using a `fake` content response when defining your endpoint, you should also provide `fake_content_type: "application/xml"` so that the parser knows to use XML parsing.
54
+
55
+
56
+ -----
57
+
58
+ [< Debugging](debugging.md)
@@ -45,22 +45,30 @@ module Flexirest
45
45
  @@base_url = value
46
46
  end
47
47
 
48
- def username(value = nil)
48
+ def username(value = nil, &block)
49
49
  @username ||= nil
50
50
  @@username ||= nil
51
51
  if value.nil?
52
- value = if @username.nil?
53
- @@username
52
+ if block_given?
53
+ @username = block
54
54
  else
55
- @username
56
- end
57
- if value.nil? && superclass.respond_to?(:username)
58
- value = superclass.username
55
+ value = if @username.nil?
56
+ @@username
57
+ else
58
+ @username
59
+ end
60
+ if value.nil? && superclass.respond_to?(:username)
61
+ value = superclass.username
62
+ end
63
+ value
59
64
  end
60
- value
61
65
  else
62
- value = CGI::escape(value) if value.present? && !value.include?("%")
63
- @username = value
66
+ if value.respond_to?(:call)
67
+ @username = value
68
+ else
69
+ value = CGI::escape(value) if value.present? && !value.include?("%")
70
+ @username = value
71
+ end
64
72
  end
65
73
  end
66
74
 
@@ -70,20 +78,28 @@ module Flexirest
70
78
  @@username = value
71
79
  end
72
80
 
73
- def password(value = nil)
81
+ def password(value = nil, &block)
74
82
  if value.nil?
75
- value = if @password.nil?
76
- @@password
83
+ if block_given?
84
+ @password = block
77
85
  else
78
- @password
79
- end
80
- if value.nil? && superclass.respond_to?(:password)
81
- value = superclass.password
86
+ value = if @password.nil?
87
+ @@password
88
+ else
89
+ @password
90
+ end
91
+ if value.nil? && superclass.respond_to?(:password)
92
+ value = superclass.password
93
+ end
94
+ value
82
95
  end
83
- value
84
96
  else
85
- value = CGI::escape(value) if value.present? && !value.include?("%")
86
- @password = value
97
+ if value.respond_to?(:call)
98
+ @password = value
99
+ else
100
+ value = CGI::escape(value) if value.present? && !value.include?("%")
101
+ @password = value
102
+ end
87
103
  end
88
104
  end
89
105
 
@@ -93,19 +93,27 @@ module Flexirest
93
93
  end
94
94
 
95
95
  def username
96
+ ret = nil
96
97
  if object_is_class?
97
- @object.username
98
+ ret = @object.username
99
+ ret = ret.call if ret.respond_to?(:call)
98
100
  else
99
- @object.class.username
101
+ ret = @object.class.username
102
+ ret = ret.call(@object) if ret.respond_to?(:call)
100
103
  end
104
+ ret
101
105
  end
102
106
 
103
107
  def password
108
+ ret = nil
104
109
  if object_is_class?
105
- @object.password
110
+ ret = @object.password
111
+ ret = ret.call if ret.respond_to?(:call)
106
112
  else
107
- @object.class.password
113
+ ret = @object.class.password
114
+ ret = ret.call(@object) if ret.respond_to?(:call)
108
115
  end
116
+ ret
109
117
  end
110
118
 
111
119
  def request_body_type
@@ -1,3 +1,3 @@
1
1
  module Flexirest
2
- VERSION = "1.6.5"
2
+ VERSION = "1.6.6"
3
3
  end
@@ -62,6 +62,19 @@ describe Flexirest::Request do
62
62
  get :all, "/"
63
63
  end
64
64
 
65
+ class AuthenticatedProcExampleClient < Flexirest::Base
66
+ base_url "http://www.example.com"
67
+ username Proc.new { |obj| obj ? "bill-#{obj.id}" : "bill" }
68
+ password do |obj|
69
+ if obj
70
+ "jones-#{obj.id}"
71
+ else
72
+ "jones"
73
+ end
74
+ end
75
+ get :all, "/"
76
+ end
77
+
65
78
  class ProcDefaultExampleClient < Flexirest::Base
66
79
  base_url "http://www.example.com"
67
80
  get :all, "/", defaults: (Proc.new do |params|
@@ -167,6 +180,21 @@ describe Flexirest::Request do
167
180
  AuthenticatedExampleClient.all
168
181
  end
169
182
 
183
+ it "should get an HTTP connection with authentication using procs when called in a class context" do
184
+ connection = double(Flexirest::Connection).as_null_object
185
+ expect(Flexirest::ConnectionManager).to receive(:get_connection).with("http://bill:jones@www.example.com").and_return(connection)
186
+ expect(connection).to receive(:get).with("/", an_instance_of(Hash)).and_return(::FaradayResponseMock.new(OpenStruct.new(body:'{"result":true}', response_headers:{})))
187
+ AuthenticatedProcExampleClient.all
188
+ end
189
+
190
+ it "should get an HTTP connection with authentication using procs when called in an object context" do
191
+ connection = double(Flexirest::Connection).as_null_object
192
+ expect(Flexirest::ConnectionManager).to receive(:get_connection).with("http://bill-1:jones-1@www.example.com").and_return(connection)
193
+ expect(connection).to receive(:get).with("/?id=1", an_instance_of(Hash)).and_return(::FaradayResponseMock.new(OpenStruct.new(body:'{"result":true}', response_headers:{})))
194
+ obj = AuthenticatedProcExampleClient.new(id: 1)
195
+ obj.all
196
+ end
197
+
170
198
  it "should get an HTTP connection when called and call get on it" do
171
199
  expect_any_instance_of(Flexirest::Connection).to receive(:get).with("/", an_instance_of(Hash)).and_return(::FaradayResponseMock.new(OpenStruct.new(body:'{"result":true}', response_headers:{})))
172
200
  ExampleClient.all
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.6.5
4
+ version: 1.6.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: 2018-03-19 00:00:00.000000000 Z
11
+ date: 2018-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -218,19 +218,47 @@ files:
218
218
  - ".simplecov"
219
219
  - ".travis.yml"
220
220
  - CHANGELOG.md
221
- - CONTRIBUTING.md
222
221
  - Gemfile
223
222
  - LICENSE.txt
224
- - Migrating-from-ActiveRestClient.md
225
223
  - README.md
226
224
  - Rakefile
227
- - Ruby-on-Rails-Integration.md
228
225
  - docs/FLEXIREST.svg
229
226
  - docs/FLEXIREST@2x.png
230
- - docs/Flexirest Internals.graffle
231
227
  - docs/Flexirest Internals.png
232
- - docs/Flexirest Logo.sketch
228
+ - docs/associations.md
229
+ - docs/authentication.md
230
+ - docs/automatic-conversion-of-fields-to-datedatetime.md
231
+ - docs/basic-usage.md
232
+ - docs/body-types.md
233
+ - docs/caching.md
234
+ - docs/combined-example.md
235
+ - docs/debugging.md
236
+ - docs/default-parameters.md
237
+ - docs/faking-calls.md
238
+ - docs/faraday-configuration.md
239
+ - docs/filtering-result-lists.md
240
+ - docs/httpparse-error-handling.md
241
+ - docs/internals.md
233
242
  - docs/issue_template.md
243
+ - docs/json-api.md
244
+ - docs/lazy-loading.md
245
+ - docs/migrating-from-activerestclient.md
246
+ - docs/parallel-requests.md
247
+ - docs/per-request-parameter-encoding.md
248
+ - docs/per-request-timeouts.md
249
+ - docs/plain-requests.md
250
+ - docs/proxying-apis.md
251
+ - docs/raw-requests.md
252
+ - docs/required-parameters.md
253
+ - docs/root-elements.md
254
+ - docs/ruby-on-rails-integration.md
255
+ - docs/source/Flexirest Internals.graffle
256
+ - docs/source/Flexirest Logo.sketch
257
+ - docs/translating-apis.md
258
+ - docs/updating-only-changed-dirty-attributes.md
259
+ - docs/using-callbacks.md
260
+ - docs/validation.md
261
+ - docs/xml-responses.md
234
262
  - flexirest.gemspec
235
263
  - lib/flexirest.rb
236
264
  - lib/flexirest/associations.rb