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
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Flexirest
2
2
 
3
+ > Access your REST APIs in a flexible way.
4
+ >
5
+ > Write your API classes in an ActiveRecord-style; like ActiveResource but Flexirest works where the resource naming doesn't follow Rails conventions, it has built-in caching and is much more flexible.
6
+
3
7
  [![Build Status](https://travis-ci.org/flexirest/flexirest.svg?branch=master)](https://travis-ci.org/flexirest/flexirest)
4
8
  [![Coverage Status](https://coveralls.io/repos/github/flexirest/flexirest/badge.svg?branch=master)](https://coveralls.io/github/flexirest/flexirest?branch=master)
5
9
  [![Code Climate](https://codeclimate.com/github/flexirest/flexirest.png)](https://codeclimate.com/github/flexirest/flexirest)
@@ -7,58 +11,13 @@
7
11
  [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/flexirest/flexirest.svg)](http://isitmaintained.com/project/flexirest/flexirest "Average time to resolve an issue")
8
12
  [![Percentage of issues still open](http://isitmaintained.com/badge/open/flexirest/flexirest.svg)](http://isitmaintained.com/project/flexirest/flexirest "Percentage of issues still open")
9
13
 
10
- This gem is for accessing REST services in an ActiveRecord style. ActiveResource already exists for this, but it doesn't work where the resource naming doesn't follow Rails conventions, it doesn't have in-built caching and it's not as flexible in general.
11
-
12
- If you are a previous user of ActiveRestClient, there's some more information on [why I created this fork and how to upgrade](https://github.com/flexirest/flexirest/blob/master/Migrating-from-ActiveRestClient.md).
14
+ ## Background
13
15
 
14
- - [Installation](#installation)
15
- - [Basic Usage](#usage)
16
- - [Create a new person](#create-a-new-person)
17
- - [Find a person](#find-a-person-not-needed-after-creating)
18
- - [Update a person](#update-a-person)
19
- - [Get all people](#get-all-people)
20
- - [Ruby on Rails Integration](#ruby-on-rails-integration)
21
- - [Advanced Features](#advanced-features)
22
- - [Faraday Configuration](#faraday-configuration)
23
- - [Associations](#associations)
24
- - [Association Type 1 - Loading Other Classes](#association-type-1-loading-other-classes)
25
- - [Association Type 2 - Lazy Loading From Other URLs](#association-type-2-lazy-loading-from-other-urls)
26
- - [Association Type 3 - HAL Auto-loaded Resources](#association-type-3-hal-auto-loaded-resources)
27
- - [Association Type 4 - Nested Resources](#association-type-4-nested-resources)
28
- - [Association Type 5 - JSON API Auto-loaded Resources](#association-type-5-json-api-auto-loaded-resources)
29
- - [Combined Example](#combined-example)
30
- - [Caching](#caching)
31
- - [Using callbacks](#using-callbacks)
32
- - [Lazy Loading](#lazy-loading)
33
- - [Authentication](#authentication)
34
- - [Basic](#basic)
35
- - [Api-Auth](#api-auth)
36
- - [Body Types](#body-types)
37
- - [Parallel Requests](#parallel-requests)
38
- - [Faking Calls](#faking-calls)
39
- - [Per-request Timeouts](#per-request-timeouts)
40
- - [Per-request Params Encoding](#per-request-params-encoding)
41
- - [Automatic Conversion of Fields to Date/DateTime](#automatic-conversion-of-fields-to-datedatetime)
42
- - [Raw Requests](#raw-requests)
43
- - [Plain Requests](#plain-requests)
44
- - [JSON API](#json-api)
45
- - [Proxying APIs](#proxying-apis)
46
- - [Translating APIs](#translating-apis)
47
- - [Default Parameters](#default-parameters)
48
- - [Root elements](#root-elements)
49
- - [Required Parameters](#required-parameters)
50
- - [Updating Only Changed/Dirty](#updating-only-changed-dirty)
51
- - [HTTP/Parse Error Handling](#httpparse-error-handling)
52
- - [Validation](#validation)
53
- - [Permitting nil values](#permitting-nil-values)
54
- - [Filtering result lists](#filtering-result-lists)
55
- - [Debugging](#debugging)
56
- - [XML Responses](#xml-responses)
57
- - [Contributing](#contributing)
16
+ If you are a previous user of [ActiveRestClient](https://github.com/whichdigital/active-rest-client), there's some more information on [why I created this fork and how to upgrade](docs/migrating-from-activerestclient.md) - but long story short, I wrote most of the code as a previous gem for a client, they agreed to open-source it, then it was abandoned and I relaunched it and now support it myself (and the owners of ActiveRestClient have acknowledged their gem is deprecated in favour of Flexirest).
58
17
 
59
- ## Installation
18
+ ## Quickstart
60
19
 
61
- Add this line to your application's Gemfile:
20
+ Assuming you're using [Bundler](http://bundler.io), to use Flexirest add this line to your application's Gemfile:
62
21
 
63
22
  ```ruby
64
23
  gem 'flexirest'
@@ -70,26 +29,12 @@ And then execute:
70
29
  $ bundle
71
30
  ```
72
31
 
73
- Or install it yourself as:
74
-
75
- ```
76
- $ gem install flexirest
77
- ```
78
-
79
- ## Usage
80
-
81
- First you need to create your new model class:
32
+ To use it, let's create a new model class:
82
33
 
83
34
  ```ruby
84
- # config/environments/production.rb
85
- Rails.application.configure do
86
- # ...
87
- config.api_server_url = "https://www.example.com/api/v1"
88
- end
89
-
90
35
  # app/models/person.rb
91
36
  class Person < Flexirest::Base
92
- base_url Rails.application.config.api_server_url
37
+ base_url "https://www.example.com/api/v1"
93
38
 
94
39
  get :all, "/people"
95
40
  get :find, "/people/:id"
@@ -99,14 +44,6 @@ class Person < Flexirest::Base
99
44
  end
100
45
  ```
101
46
 
102
- Note I've specified the base_url in the class above. This is useful where you want to be explicit or use different APIs for some classes and be explicit. If you have one server that's generally used, you can set it once with a simple line in a `config/initializer/{something}.rb` file:
103
-
104
- ```ruby
105
- Flexirest::Base.base_url = "https://www.example.com/api/v1"
106
- ```
107
-
108
- Any `base_url` settings in specific classes override this declared default. You can also assign an array of URLs to `base_url` and Flexirest will randomly pull one of the URLs for each request, giving you a very simplistic load balancing (it doesn't know about the health or load levels of the backends).
109
-
110
47
  You can then use your new class like this:
111
48
 
112
49
  ```ruby
@@ -131,1187 +68,99 @@ id = @person.id
131
68
  end
132
69
  ```
133
70
 
134
- For `delete` requests whether an API can handle a body or not is undefined. The default is to ignore any parameters not sent in the URL named parameters, but you can optionally specify `send_delete_body` and it will encode them in your chosen way into the body.
135
-
136
- ```
137
- delete :remove, "/people/:id", send_delete_body: true
138
- ```
139
-
140
- If an API returns an array of results and you have [will_paginate](https://rubygems.org/gems/will_paginate) installed then you can call the paginate method to return a particular page of the results (note: this doesn't reduce the load on the server, but it can help with pagination if you have a cached response).
141
-
142
- ```ruby
143
- @people = Person.all
144
- @people.paginate(page: 1, per_page: 10).each do |person|
145
- puts "You made the first page: " + person.first_name
146
- end
147
- ```
148
-
149
- Note, you can assign to any attribute, whether it exists or not before and read from any attribute (which will return nil if not found). If you pass a string or a number to a method it will assume that it's for the "id" field. Any other field values must be passed as a hash and you can't mix passing a string/number and a hash.
150
-
151
- ```ruby
152
- @person = Person.find(1234) # valid
153
- @person = Person.find("1234") # valid
154
- @person = Person.find(:id => 1234) # valid
155
- @person = Person.find(:id => 1234, :name => "Billy") # valid
156
- @person = Person.find(1234, :name => "Billy") # invalid
157
- ```
158
-
159
- You can also call any mapped method as an instance variable which will pass the current attribute set in as parameters (either GET or POST depending on the mapped method type). If the method returns a single instance it will assign the attributes of the calling object and return itself. If the method returns a list of instances, it will only return the list. So, we could rewrite the create call above as:
160
-
161
- ```ruby
162
- @person = Person.new
163
- @person.first_name = "John"
164
- @person.last_name = "Smith"
165
- @person.create
166
- puts @person.id
167
- ```
168
-
169
- The response of the #create call set the attributes at that point (any manually set attributes before that point are removed).
170
-
171
- If you have attributes beginning with a number, Ruby doesn't like this. So, you can use hash style notation to read/write the attributes:
172
-
173
- ```ruby
174
- @tv = Tv.find(model:"UE55U8000") # { "properties" : {"3d" : false} }
175
- puts @tv.properties["3d"]
176
- @tv.properties["3d"] = true
177
- ```
178
-
179
- If you want to debug the response, using inspect on the response object may well be useful. However, if you want a simpler output, then you can call `#to_json` on the response object:
180
-
181
- ```ruby
182
- @person = Person.find(email:"something@example.com")
183
- puts @person.to_json
184
- ```
185
-
186
- ### Ruby on Rails Integration
187
-
188
- A detailed guide, how to integrate Flexirest with a RESTful resources can be found in the [Ruby-on-Rails-Integration.md](https://github.com/flexirest/flexirest/blob/master/Ruby-on-Rails-Integration.md).
189
-
190
- ## Advanced Features
191
-
192
- ### Faraday Configuration
193
-
194
- Flexirest uses Faraday to allow switching HTTP backends, the default is to just use Faraday's default. To change the used backend just set it in the class by setting `adapter` to a Faraday supported adapter symbol.
195
-
196
- ```ruby
197
- Flexirest::Base.adapter = :net_http
198
- # or ...
199
- Flexirest::Base.adapter = :patron
200
- ```
201
-
202
- In versions before 1.2.0 the adapter was hardcoded to `:patron`, so if you want to ensure it still uses Patron, you should set this setting.
203
-
204
- If you want more control you can pass a **complete** configuration block ("complete" means that the block does not _override_ [the default configuration](https://github.com/flexirest/flexirest/blob/5b1953d89e26c02ca74f74464ccb7cd4c9439dcc/lib/flexirest/configuration.rb#L184-L201), but rather _replaces_ it). For available config variables look into the Faraday documentation.
205
-
206
- ```ruby
207
- Flexirest::Base.faraday_config do |faraday|
208
- faraday.adapter(:net_http)
209
- faraday.options.timeout = 10
210
- faraday.headers['User-Agent'] = "Flexirest/#{Flexirest::VERSION}"
211
- end
212
- ```
213
-
214
- ### Associations
215
-
216
- There are two types of association. One assumes when you call a method you actually want it to call the method on a separate class (as that class has other methods that are useful). The other is lazy loading related classes from a separate URL.
217
-
218
- #### Association Type 1 - Loading Other Classes
219
-
220
- If the call would return a single instance or a list of instances that should be considered another object, you can also specify this when mapping the method using the `:has_one` or `:has_many` options respectively. It doesn't call anything on that object except for instantiate it, but it does let you have objects of a different class to the one you initially called.
221
-
222
- ```ruby
223
- class Expense < Flexirest::Base
224
- def inc_vat
225
- ex_vat * 1.20
226
- end
227
- end
228
-
229
- class Address < Flexirest::Base
230
- def full_string
231
- return "#{self.street}, #{self.city}, #{self.region}, #{self.country}"
232
- end
233
- end
234
-
235
- class Person < Flexirest::Base
236
- get :find, "/people/:id", :has_many => {:expenses => Expense}, :has_one => {:address => Address}
237
- end
238
-
239
- @person = Person.find(1)
240
- puts @person.expenses.reduce {|e| e.inc_vat}
241
- puts @person.address.full_string
242
- ```
243
-
244
- You can also use `has_one`/`has_many` on the class level to allow chaining of classes. You can specify the class name or allow the system to automatically convert it to the singular class. For example:
245
-
246
- ```ruby
247
- class Expense < Flexirest::Base
248
- def inc_vat
249
- ex_vat * 1.20
250
- end
251
- end
252
-
253
- class Address < Flexirest::Base
254
- def full_string
255
- return "#{self.street}, #{self.city}, #{self.region}, #{self.country}"
256
- end
257
- end
258
-
259
- class Person < Flexirest::Base
260
- has_one :addresses
261
- has_many :expenses, Expense
262
- get :find, "/people/:id"
263
- end
264
-
265
- class Company < Flexirest::Base
266
- has_many :people
267
- get :find, "/companies/:id"
268
- end
269
- ```
270
-
271
- Sometimes we want attributes to just return a simple Ruby Array. To achieve this we can add an `:array` option to the method. This is especially useful when the attribute contains an array of scalar values. If you don't specify the `:array` option Flexirest will return a `Flexirest::ResultIterator`. To illustrate the difference consider the following example:
272
-
273
- ```ruby
274
- class Book < Flexirest::Base
275
- # :authors attribute ["Robert T. Kiyosaki", "Sharon L. Lechter C.P.A"]
276
- # :genres attribute ["self-help", "finance", "education"]
277
- get :find, "/books/:name", array: [:authors]
278
- end
279
- ```
280
-
281
- In the example above, the following results can be observed:
282
-
283
- ```ruby
284
- @book = Book.find("rich-dad-poor-dad")
285
- puts @book.authors
286
- #=> ["Robert T. Kiyosaki", "Sharon L. Lechter C.P.A"]
287
- puts @book.authors.class
288
- #=> Array
289
- puts @book.genres
290
- #=> #<Flexirest::ResultIterator:0x007ff420fe7a88 @_status=nil, @_headers=nil, @items=["self-help", "finance", "education"]>
291
- puts @books.genres.class
292
- #=> Flexirest::ResultIterator
293
- puts @books.genres.items
294
- #=> ["self-help", "finance", "education"]
295
- ```
296
-
297
- When the `:array` option includes an attribute, it is assumed the values were returned with the request, and they will not be lazily loaded. It is also assumed the attribute values do not map to a Flexirest resource.
298
-
299
- #### Association Type 2 - Lazy Loading From Other URLs
300
-
301
- When mapping the method, passing a list of attributes will cause any requests for those attributes to mapped to the URLs given in their responses. The response for the attribute may be one of the following:
302
-
303
- ```ruby
304
- "attribute" : "URL"
305
- "attribute" : ["URL", "URL"]
306
- "attribute" : { "url" : "URL"}
307
- "attribute" : { "href" : "URL"}
308
- "attribute" : { "something" : "URL"}
309
- ```
310
-
311
- The difference between the last 3 examples is that a key of `url` or `href` signifies it's a single object that is lazy loaded from the value specified. Any other keys assume that it's a nested set of URLs (like in the array situation, but accessible via the keys - e.g. object.attribute.something in the above example).
312
-
313
- It is required that the URL is a complete URL including a protocol starting with "http". To configure this use code like:
314
-
315
- ```ruby
316
- class Person < Flexirest::Base
317
- get :find, "/people/:id", :lazy => [:orders, :refunds]
318
- end
319
- ```
320
-
321
- And use it like this:
322
-
323
- ```ruby
324
- # Makes a call to /people/1
325
- @person = Person.find(1)
326
-
327
- # Makes a call to the first URL found in the "books":[...] array in the article response
328
- # only makes the HTTP request when first used though
329
- @person.books.first.name
330
- ```
331
-
332
- #### Association Type 3 - HAL Auto-loaded Resources
333
-
334
- You don't need to define lazy attributes if they are defined using [HAL](http://stateless.co/hal_specification.html) (with an optional embedded representation). If your resource has an `_links` item (and optionally an `_embedded` item) then it will automatically treat the linked resources (with the `_embedded` cache) as if they were defined using `:lazy` as per type 2 above.
335
-
336
- If you need to, you can access properties of the HAL association. By default just using the HAL association gets the embedded resource (or requests the remote resource if not available in the `_embedded` list).
337
-
338
- ```ruby
339
- @person = Person.find(1)
340
- @person.students[0]._hal_attributes("title")
341
- ```
342
-
343
- #### Association Type 4 - Nested Resources
344
-
345
- It's common to have resources that are logically children of other resources. For example, suppose that your API includes these endpoints:
346
-
347
- | HTTP Verb | Path | |
348
- |-----------|-----------------------------|------------------------------------------|
349
- | POST | /magazines/:magazine_id/ads | create a new ad belonging to a magazine |
350
- | GET | /magazines/:magazine_id/ads | display a list of all ads for a magazine |
351
-
352
- In these cases, your child class will contain the following:
353
-
354
- ```ruby
355
- class Ad < Flexirest::Base
356
- post :create, "/magazines/:magazine_id/ads"
357
- get :all, "/magazines/:magazine_id/ads"
358
- end
359
- ```
360
-
361
- You can then access Ads by specifying their magazine IDs:
362
-
363
- ```ruby
364
- Ad.all(magazine_id: 1)
365
- Ad.create(magazine_id: 1, title: "My Add Title")
366
- ```
367
-
368
- #### Association Type 5 - JSON API Auto-loaded Resources
369
-
370
- If attributes are defined using [JSON API](http://jsonapi.org), you don't need to define lazy attributes. If your resource has a `links` object with a `related` item, it will automatically treat the linked resources as if they were defined using `:lazy`.
371
-
372
- You need to activate JSON API by specifying the `json_api` proxy:
373
-
374
- ```ruby
375
- class Article < Flexirest::Base
376
- proxy :json_api
377
- end
378
- ```
379
-
380
- If you want to embed linked resources directly in the response (i.e. request a JSON API compound document), use the `includes` class method. The linked resource is accessed in the same was as if it was lazily loaded, but without the extra request:
381
-
382
- ```ruby
383
- # Makes a call to /articles with parameters: include=images
384
- Article.includes(:images).all
385
-
386
- # For nested resources, the include parameter becomes: include=images.tags,images.photographer
387
- Article.includes(:images => [:tags, :photographer]).all
388
- ```
389
-
390
- #### Combined Example
391
-
392
- OK, so let's say you have an API for getting articles. Each article has a property called `title` (which is a string) and a property `images` which includes a list of URIs. Following this URI would take you to a image API that returns the image's `filename` and `filesize`. We'll also assume this is a HAL compliant API. We would declare our two models (one for articles and one for images) like the following:
393
-
394
- ```ruby
395
- class Article < Flexirest::Base
396
- get :find, '/articles/:id', has_many:{:images => Image} # ,lazy:[:images] isn't needed as we're using HAL
397
- end
398
-
399
- class Image < Flexirest::Base
400
- # You may have mappings here
401
-
402
- def nice_size
403
- "#{size/1024}KB"
404
- end
405
- end
406
- ```
407
-
408
- We assume the /articles/:id call returns something like the following:
409
-
410
- ```json
411
- {
412
- "title": "Fly Fishing",
413
- "author": "J R Hartley",
414
- "images": [
415
- "http://api.example.com/images/1",
416
- "http://api.example.com/images/2"
417
- ]
418
- }
419
- ```
420
-
421
- We said above that the /images/:id call would return something like:
422
-
423
- ```json
424
- {
425
- "filename": "http://cdn.example.com/images/foo.jpg",
426
- "filesize": 123456
427
- }
428
- ```
429
-
430
- When it comes time to use it, you would do something like this:
431
-
432
- ```ruby
433
- @article = Article.find(1)
434
- @article.images.is_a?(Flexirest::LazyAssociationLoader)
435
- @article.images.size == 2
436
- @article.images.each do |image|
437
- puts image.inspect
438
- end
439
- ```
440
-
441
- At this point, only the HTTP call to '/articles/1' has been made. When you actually start using properties of the images list/image object then it makes a call to the URL given in the images list and you can use the properties as if it was a nested JSON object in the original response instead of just a URL:
442
-
443
- ```ruby
444
- @image = @article.images.first
445
- puts @image.filename
446
- # => http://cdn.example.com/images/foo.jpg
447
- puts @image.filesize
448
- # => 123456
449
- ```
450
-
451
- You can also treat `@image` looks like an Image class (and you should 100% treat it as one) it's technically a lazy loading proxy. So, if you cache the views for your application should only make HTTP API requests when actually necessary.
452
-
453
- ```ruby
454
- puts @image.nice_size
455
- # => 121KB
456
- ```
457
-
458
- ### Caching
459
-
460
- Expires and ETag based caching is enabled by default, but with a simple line in the application.rb/production.rb you can disable it:
461
-
462
- ```ruby
463
- Flexirest::Base.perform_caching = false
464
- ```
465
-
466
- or you can disable it per classes with:
467
-
468
- ```ruby
469
- class Person < Flexirest::Base
470
- perform_caching false
471
- end
472
- ```
473
-
474
- 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:
475
-
476
- ```ruby
477
- Flexirest::Base.cache_store = Redis::Store.new("redis://localhost:6379/0/cache")
478
- ```
479
-
480
- ### Using callbacks
481
-
482
- 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).
483
-
484
- 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).
485
-
486
- ```ruby
487
- require 'secure_random'
488
-
489
- class Person < Flexirest::Base
490
- before_request do |name, request|
491
- if request.post? || name == :save
492
- id = request.post_params.delete(:id)
493
- request.get_params[:id] = id
494
- end
495
- end
496
-
497
- before_request :replace_token_in_url
498
-
499
- before_request :add_authentication_details
500
-
501
- before_request :replace_body
502
-
503
- before_request :override_default_content_type
504
-
505
- private
506
-
507
- def replace_token_in_url(name, request)
508
- request.url.gsub!("#token", SecureRandom.hex)
509
- end
510
-
511
- def add_authentication_details(name, request)
512
- request.headers["X-Custom-Authentication-Token"] = ENV["AUTH_TOKEN"]
513
- end
514
-
515
- def replace_body(name, request)
516
- if name == :create
517
- request.body = request.post_params.to_json
518
- end
519
- end
520
-
521
- def override_default_content_type(name, request)
522
- if name == :save
523
- request.headers["Content-Type"] = "application/json"
524
- end
525
- end
526
- end
527
- ```
528
-
529
- If you need to, you can create a custom parent class with a `before_request` callback and all children will inherit this callback.
530
-
531
- ```ruby
532
- class MyProject::Base < Flexirest::Base
533
- before_request do |name, request|
534
- request.get_params[:api_key] = "1234567890-1234567890"
535
- end
536
- end
537
-
538
- class Person < MyProject::Base
539
- # No need to declare a before_request for :api_key, already defined by the parent
540
- end
541
- ```
542
-
543
- After callbacks work in exactly the same way:
544
-
545
- ```ruby
546
- class Person < Flexirest::Base
547
- get :all, "/people"
548
-
549
- after_request :fix_empty_content
550
- after_request :cache_all_people
551
-
552
- private
553
-
554
- def fix_empty_content(name, response)
555
- if response.status == 204 && response.body.blank?
556
- response.body = '{"empty": true}'
557
- end
558
- end
559
-
560
- def cache_all_people(name, response)
561
- if name == :all
562
- response.response_headers["Expires"] = 1.hour.from_now.iso8601
563
- end
564
- end
565
- end
566
- ```
567
-
568
- **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.
569
-
570
- 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.
571
-
572
- ```ruby
573
- class Person < Flexirest::Base
574
- get :all, "/people"
575
-
576
- after_request :fix_invalid_request
577
-
578
- private
579
-
580
- def fix_invalid_request(name, response)
581
- if response.status == 401
582
- # Do something to fix the state of caches/variables used in the
583
- # before_request, etc
584
- return :retry
585
- end
586
- end
587
- end
588
- ```
589
-
590
- ### Lazy Loading
71
+ ## Reading further
72
+
73
+ I've written a TON of documentation on how to use Flexirest and a LITTLE bit on how it works internally. For more information see the following pages:
74
+
75
+ - [Basic Usage](docs/basic-usage.md)
76
+ - [Ruby on Rails integration](docs/ruby-on-rails-integration.md)
77
+ - [Faraday configuration](docs/faraday-configuration.md)
78
+ - [Associations](docs/associations.md)
79
+ - [Combined example](docs/combined-example.md)
80
+ - [Caching](docs/caching.md)
81
+ - [Using callbacks](docs/using-callbacks.md)
82
+ - [Lazy loading](docs/lazy-loading.md)
83
+ - [Authentication](docs/authentication.md)
84
+ - [Body types](docs/body-types.md)
85
+ - [Parallel requests](docs/parallel-requests.md)
86
+ - [Faking calls](docs/faking-calls.md)
87
+ - [Per-request timeouts](docs/per-request-timeouts.md)
88
+ - [Per-request parameter encoding](docs/per-request-parameter-encoding.md)
89
+ - [Automatic conversion of fields to Date/DateTime](docs/automatic-conversion-of-fields-to-datedatetime.md)
90
+ - [Raw requests](docs/raw-requests.md)
91
+ - [Plain requests](docs/plain-requests.md)
92
+ - [JSON API](docs/json-api.md)
93
+ - [Proxying APIs](docs/proxying-apis.md)
94
+ - [Translating APIs - DEPRECATED](docs/translating-apis.md)
95
+ - [Default parameters](docs/default-parameters.md)
96
+ - [Root elements](docs/root-elements.md)
97
+ - [Required parameters](docs/required-parameters.md)
98
+ - [Updating only changed/dirty attributes](docs/updating-only-changed-dirty-attributes.md)
99
+ - [HTTP/parse error handling](docs/httpparse-error-handling.md)
100
+ - [Validation](docs/validation.md)
101
+ - [Filtering result lists](docs/filtering-result-lists.md)
102
+ - [Debugging](docs/debugging.md)
103
+ - [XML responses](docs/xml-responses.md)
104
+
105
+
106
+ ## Licence
107
+
108
+ Code originally copyright© 2013 Which? Ltd, released with an MIT License and now this fork is released under the same licence by Andy Jeffries.
109
+
110
+ **MIT License**
111
+
112
+ Permission is hereby granted, free of charge, to any person obtaining
113
+ a copy of this software and associated documentation files (the
114
+ "Software"), to deal in the Software without restriction, including
115
+ without limitation the rights to use, copy, modify, merge, publish,
116
+ distribute, sublicense, and/or sell copies of the Software, and to
117
+ permit persons to whom the Software is furnished to do so, subject to
118
+ the following conditions:
119
+
120
+ The above copyright notice and this permission notice shall be
121
+ included in all copies or substantial portions of the Software.
122
+
123
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
124
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
125
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
126
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
127
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
128
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
129
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
591
130
 
592
- Flexirest supports lazy loading (delaying the actual API call until the response is actually used, so that views can be cached without still causing API calls).
131
+ ## Contributing
593
132
 
594
- **Note: Currently this isn't enabled by default, but this is likely to change in the future to make lazy loading the default.**
133
+ ### Bug Reports & Feature Requests
595
134
 
596
- To enable it, simply call the lazy_load! method in your class definition:
135
+ Please use the [issue tracker](https://github.com/flexirest/flexirest/issues) to report any bugs or file feature requests.
597
136
 
598
- ```ruby
599
- class Article < Flexirest::Base
600
- lazy_load!
601
- end
602
- ```
137
+ If you want to generate a reproducible bug report, here are some steps to follow in your bug report:
603
138
 
604
- If you have a ResultIterator that has multiple objects, each being lazy loaded or HAL linked resources that isn't loaded until it's used, you can actually parallelise the fetching of the items using code like this:
139
+ 1. Clone the Flexirest repo (`git clone https://github.com/flexirest/flexirest.git`)
140
+ 2. Change to the flexirest folder (`cd flexirest`)
141
+ 3. Install any dependencies (`bundle install`)
142
+ 4. Run an interactive console (`rake c`)
143
+ 5. Paste the following:
605
144
 
606
145
  ```ruby
607
- items.parallelise(:id)
608
-
609
- # or
610
-
611
- items.parallelise do |item|
612
- item.id
146
+ class MyObject < Flexirest::Base
147
+ base_url "https://requestb.in"
148
+ post :create, "/{CREATE_A_PASTEBIN_ID}"
613
149
  end
614
- ```
615
-
616
- This will return an array of the named method for each object or the response from the block and will have loaded the objects in to the resource.
617
-
618
- ### Authentication
619
-
620
- #### Basic
621
-
622
- You can authenticate with Basic authentication by putting the username and password in to the `base_url` or by setting them within the specific model:
623
150
 
624
- ```ruby
625
- class Person < Flexirest::Base
626
- username 'api'
627
- password 'eb693ec-8252c-d6301-02fd0-d0fb7-c3485'
628
-
629
- # ...
630
- end
151
+ my_object = MyObject.new(params: "something")
152
+ my_object.create
153
+ # => something is output to the terminal
631
154
  ```
632
155
 
633
- #### Api-Auth
634
-
635
- 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 in 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.
636
-
637
- ```ruby
638
- require 'api-auth'
639
-
640
- @access_id = '123456'
641
- @secret_key = 'abcdef'
642
- Flexirest::Base.api_auth_credentials(@access_id, @secret_key)
643
- ```
156
+ ### Working on the codebase
644
157
 
645
- You can also specify different credentials for different models just like configuring base_url
158
+ The general steps are the same as for almost all codebases on GitHub:
646
159
 
647
- ```ruby
648
- class Person < Flexirest::Base
649
- api_auth_credentials('123456', 'abcdef')
650
- end
651
- ```
160
+ 1. Fork it and clone your fork
161
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
162
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
163
+ 4. Push to the branch (`git push origin my-new-feature`)
164
+ 5. Create new Pull Request on GitHub.com
652
165
 
653
- For more information on how to generate an access id and secret key please read the [Api-Auth](https://github.com/mgomes/api_auth) documentation.
654
-
655
- If you want to specify either the `:digest` or `:override_http_method` to ApiAuth, you can pass these in as options after the access ID and secret key, for example:
656
-
657
- ```ruby
658
- class Person < Flexirest::Base
659
- api_auth_credentials('123456', 'abcdef', digest: "sha256")
660
- end
661
- ```
662
-
663
- ### Body Types
664
-
665
- By default Flexirest puts the body in to normal CGI parameters in K=V&K2=V2 format. However, if you want to use JSON for your PUT/POST requests, you can use either (the other option, the default, is `:form_encoded`):
666
-
667
- ```ruby
668
- class Person < Flexirest::Base
669
- request_body_type :json
670
- # ...
671
- end
672
- ```
673
-
674
- or
675
-
676
- ```ruby
677
- Flexirest::Base.request_body_type = :json
678
- ```
679
-
680
- This will also set the header `Content-Type` to `application/x-www-form-urlencoded` by default or `application/json; charset=utf-8` when `:json`. You can override this using the callback `before_request`.
681
-
682
- If you have an API that is inconsistent in its body type requirements, you can also specify it on the individual method mapping:
683
-
684
- ```ruby
685
- class Person < Flexirest::Base
686
- request_body_type :form_encoded # This is the default, but just for demo purposes
687
-
688
- get :all, '/people', request_body_type: :json
689
- end
690
- ```
691
-
692
- ### Parallel Requests
693
-
694
- Sometimes you know you will need to make a bunch of requests and you don't want to wait for one to finish to start the next. When using parallel requests there is the potential to finish many requests all at the same time taking only as long as the single longest request. To use parallel requests you will need to set Flexirest to use a Faraday adapter that supports parallel requests [(such as Typhoeus)](https://github.com/lostisland/faraday/wiki/Parallel-requests).
695
-
696
- ```ruby
697
- # Set adapter to Typhoeus to use parallel requests
698
- Flexirest::Base.adapter = :typhoeus
699
- ```
700
-
701
- Now you just need to get ahold of the connection that is going to make the requests by specifying the same host that the models will be using. When inside the `in_parallel` block call request methods as usual and access the results after the `in_parallel` block ends.
702
-
703
- ```ruby
704
- Flexirest::ConnectionManager.in_parallel('https://www.example.com') do
705
- @person = Person.find(1234)
706
- @employers = Employer.all
707
-
708
- puts @person #=> nil
709
- puts @employers #=> nil
710
- end # The requests are all fired in parallel during this end statement
711
-
712
- puts @person.name #=> "John"
713
- puts @employers.size #=> 7
714
- ```
715
-
716
- ### Faking Calls
717
-
718
- There are times when an API hasn't been developed yet, so you want to fake the API call response. To do this, you can simply pass a `fake` option when mapping the call containing the response.
719
-
720
- ```ruby
721
- class Person < Flexirest::Base
722
- get :all, '/people', fake: [{first_name:"Johnny"}, {first_name:"Bob"}]
723
- end
724
- ```
725
-
726
- You may want to run a proc when faking data (to put information from the parameters in to the response or return different responses depending on the parameters). To do this just pass a proc to :fake:
727
-
728
- ```ruby
729
- class Person < Flexirest::Base
730
- get :all, '/people', fake: ->(request) { {result: request.get_params[:id]} }
731
- end
732
- ```
733
-
734
- ### Per-request Timeouts
735
-
736
- There are times when an API is generally quick, but one call is very intensive. You don't want to set a global timeout in the Faraday configuration block, you just want to increase the timeout for this single call. To do this, you can simply pass a `timeout` option when mapping the call containing the response (in seconds).
737
-
738
- ```ruby
739
- class Person < Flexirest::Base
740
- get :all, '/people', timeout: 5
741
- end
742
- ```
743
-
744
- ### Per-request Params Encoding
745
-
746
- When url-encoding get parameters, Rudy adds brackets([]) by default to any parameters in an Array. For example, if you tried to pass these parameters:
747
-
748
- ```ruby
749
- Person.all(param: [1, 2, 3])
750
- ```
751
-
752
- Ruby would encode the url as
753
-
754
- ```
755
- ?param[]=1&param[]=2&param[]=3
756
- ```
757
-
758
- If you prefer flattened notation instead, pass a `params_encoder` option of `:flat` when mapping the call. So this call:
759
-
760
- ```ruby
761
- class Person < Flexirest::Base
762
- get :all, '/people', params_encoder: :flat
763
- end
764
- ```
765
-
766
- would output the following url
767
-
768
- ```
769
- ?param=1&param=2&param=3
770
- ```
771
-
772
- ### Automatic Conversion of Fields to Date/DateTime
773
-
774
- By default Flexirest will attempt to convert all fields to a Date or DateTime object if it's a string and the value matches a pair of regular expressions. However, on large responses this can be computationally expensive. You can disable this automatic conversion completely with:
775
-
776
- ```ruby
777
- Flexirest::Base.disable_automatic_date_parsing = true
778
- ```
779
-
780
- Additionally, you can specify when mapping the methods which fields should be parsed (so you can disable it in general, then apply it to particular known fields):
781
-
782
- ```ruby
783
- class Person < Flexirest::Base
784
- get :all, '/people', parse_fields: [:created_at, :updated_at]
785
- end
786
- ```
787
-
788
- It is also possible to whitelist fields should be parsed in your models, which is useful if you are instantiating these objects directly. The specified fields also apply automatically to request mapping.
789
-
790
- ```ruby
791
- class Person < Flexirest::Base
792
- parse_date :updated_at, :created_at
793
- end
794
-
795
- # to disable all mapping
796
- class Disabled < Flexirest::Base
797
- parse_date :none
798
- end
799
- ```
800
-
801
- This system respects `disable_automatic_date_parsing`, and will default to mapping everything - unless a `parse_date` whitelist is specified, or automatic parsing is globally disabled.
802
-
803
- ### Raw Requests
804
-
805
- 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:
806
-
807
- ```ruby
808
- class Person < Flexirest::Base
809
- end
810
-
811
- people = Person._request('http://api.example.com/v1/people') # Defaults to get with no parameters
812
- # people is a normal Flexirest object, implementing iteration, HAL loading, etc.
813
-
814
- Person._request('http://api.example.com/v1/people', :post, {id:1234,name:"John"}) # Post with parameters
815
- ```
816
-
817
- 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:
818
-
819
- ```ruby
820
- Person._request("http://api.example.com/v1/people", :get, {}, {headers:{"X-Something": "foo/bar"}})
821
- ```
822
-
823
- If you want to use a lazy loaded request instead (so it will create an object that will only call the API if you use it), you can use `_lazy_request` instead of `_request`. If you want you can create a construct that creates and object that lazy loads itself from a given method (rather than a URL):
824
-
825
- ```ruby
826
- @person = Person._lazy_request(Person._request_for(:find, 1234))
827
- ```
828
-
829
- This initially creates an Flexirest::Request object as if you'd called `Person.find(1234)` which is then passed in to the `_lazy_request` method to return an object that will call the request if any properties are actually used. This may be useful at some point, but it's actually easier to just prefix the `find` method call with `lazy_` like:
830
-
831
- ```ruby
832
- @person = Person.lazy_find(1234)
833
- ```
834
-
835
- Doing this will try to find a literally mapped method called "lazy_find" and if it fails, it will try to use "find" but instantiate the object lazily.
836
-
837
- ### Plain Requests
838
-
839
- If you are already using Flexirest but then want to simply call a normal URL and receive the resulting content as a string (i.e. not going through JSON parsing or instantiating in to an Flexirest::Base descendent) you can use code like this:
840
-
841
- ```ruby
842
- class Person < Flexirest::Base
843
- end
844
-
845
- people = Person._plain_request('http://api.example.com/v1/people') # Defaults to get with no parameters
846
- # people is a normal Flexirest object, implementing iteration, HAL loading, etc.
847
-
848
- Person._plain_request('http://api.example.com/v1/people', :post, {id:1234,name:"John"}) # Post with parameters
849
- ```
850
-
851
- The parameters are the same as for `_request`, but it does no parsing on the response
852
-
853
- You can also bypass the response parsing using a mapped method like this:
854
-
855
- ```ruby
856
- class Person < Flexirest::Base
857
- get :all, "/v1/people", plain: true
858
- end
859
- ```
860
-
861
- The response of a plain request (from either source) is a `Flexirest::PlainResponse` which acts like a string containing the response's body, but it also has a `_headers` method that returns the HTTP response headers and a `_status` method containing the response's HTTP method.
862
-
863
- ### JSON API
864
-
865
- If you are working with a [JSON API](http://jsonapi.org), you need to activate JSON API by specifying the `json_api` proxy:
866
-
867
- ```ruby
868
- class Article < Flexirest::Base
869
- proxy :json_api
870
- end
871
- ```
872
-
873
- This proxy translates requests according to the JSON API specifications, parses responses, and retrieves linked resources. It also adds the `Accept: application/vnd.api+json` header for all requests.
874
-
875
- 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.
876
-
877
- 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:
878
-
879
- ```ruby
880
- # Makes a call to /articles with parameters: include=images
881
- Article.includes(:images).all
882
-
883
- # For nested resources, the include parameter becomes: include=images.tags,images.photographer
884
- Article.includes(:images => [:tags, :photographer]).all
885
- ```
886
-
887
- For post and patch requests, the proxy formats a JSON API complied request, and adds a `Content-Type: application/vnd.api+json` header. It guesses the `type` value in the resource object from the class name, but it can be set specifically with `alias_type`:
888
-
889
- ```ruby
890
- class Photographer < Flexirest::Base
891
- proxy :json_api
892
- # Sets the type in the resource object to "people"
893
- alias_type :people
894
-
895
- patch :update, '/photographers/:id'
896
- end
897
- ```
898
-
899
- NB: Updating relationships is not yet supported.
900
-
901
- ### Proxying APIs
902
-
903
- Sometimes you may be working with an old API that returns JSON in a less than ideal format or the URL or parameters required have changed.
904
-
905
- If it's simply that you want attribute names like `SomeName` or `someName` to be more Ruby-style `some_name` then you can simply do that by setting `:rubify_names` when mapping an API call.
906
-
907
- ```ruby
908
- class Article < Flexirest::Base
909
- base_url "http://www.example.com"
910
-
911
- get :all, "/all", rubify_names: true
912
- end
913
- ```
914
-
915
- In more complex cases you can define a descendent of `Flexirest::ProxyBase`, pass it to your model as the proxy and have it rework URLs/parameters on the way out and the response on the way back in (already converted to a Ruby hash/array). By default any non-proxied URLs are just passed through to the underlying connection layer. For example:
916
-
917
- ```ruby
918
- class ArticleProxy < Flexirest::ProxyBase
919
- get "/all" do
920
- url "/all_people" # Equiv to url.gsub!("/all", "/all_people") if you wanted to keep params
921
- response = passthrough
922
- translate(response) do |body|
923
- body["first_name"] = body.delete("fname")
924
- body
925
- end
926
- end
927
- end
928
-
929
- class Article < Flexirest::Base
930
- proxy ArticleProxy
931
- base_url "http://www.example.com"
932
-
933
- get :all, "/all", fake:"{\"name\":\"Billy\"}"
934
- get :list, "/list", fake:"[{\"name\":\"Billy\"}, {\"name\":\"John\"}]"
935
- end
936
-
937
- Article.all.first_name == "Billy"
938
- ```
939
-
940
- This example does two things:
941
-
942
- 1. It rewrites the incoming URL for any requests matching "_/all_" to "/all_people"
943
- 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)
944
-
945
- 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.
946
-
947
- You can use the `get_params` or `post_params` methods within your proxy block to amend/create/delete items from those request parameters, like this:
948
-
949
- ```ruby
950
- get "/list" do
951
- get_params["id"] = get_params.delete("identifier")
952
- passthrough
953
- end
954
- ```
955
-
956
- This example renames the get_parameter for the request from `identifier` to `id` (the same would have worked with post_params if it was a POST/PUT request). The `passthrough` method will take care of automatically recombining them in to the URL or encoding them in to the body as appropriate.
957
-
958
- If you want to manually set the body for the API yourself you can use the `body` method
959
-
960
- ```ruby
961
- put "/update" do
962
- body "{\"id\":#{post_params["id"]}}"
963
- passthrough
964
- end
965
- ```
966
-
967
- This example takes the `post_params["id"]` and converts the body from being a normal form-encoded body in to being a JSON body.
968
-
969
- The proxy block expects one of three things to be the return value of the block.
970
-
971
- 1. The first options is that the call to `passthrough` is the last thing and it calls down to the connection layer and returns the actual response from the server in to the "API->Object" mapping layer ready for use in your application
972
- 2. The second option is to save the response from `passthrough` and use `translate` on it to alter the structure.
973
- 3. The third option is to use `render` if you want to completely fake an API and return the JSON yourself
974
-
975
- To completely fake the API, you can do the following. Note, this is also achievable using the `fake` setting when mapping a method, however by doing it in a Proxy block means you can dynamically generate the JSON rather than just a hard coded string.
976
-
977
- ```ruby
978
- put "/fake" do
979
- render "{\"id\":1234}"
980
- end
981
- ```
982
-
983
- ### Translating APIs
984
-
985
- **IMPORTANT: This functionality has been deprecated in favour of the "Proxying APIs" functionality above. You should aim to remove this from your code as soon as possible.**
986
-
987
- Sometimes you may be working with an API that returns JSON in a less than ideal format. In this case you can define a barebones class and pass it to your model. The Translator class must have class methods that are passed the JSON object and should return an object in the correct format. It doesn't need to have a method unless it's going to translate that mapping though (so in the example below there's no list method). For example:
988
-
989
- ```ruby
990
- class ArticleTranslator
991
- def self.all(object)
992
- ret = {}
993
- ret["first_name"] = object["name"]
994
- ret
995
- end
996
- end
997
-
998
- class Article < Flexirest::Base
999
- translator ArticleTranslator
1000
- base_url "http://www.example.com"
1001
-
1002
- get :all, "/all", fake:"{\"name\":\"Billy\"}"
1003
- get :list, "/list", fake:"[{\"name\":\"Billy\"}, {\"name\":\"John\"}]"
1004
- end
1005
-
1006
- Article.all.first_name == "Billy"
1007
- ```
1008
-
1009
- ### Default Parameters
1010
-
1011
- If you want to specify default parameters you shouldn't use a path like:
1012
-
1013
- ```ruby
1014
- class Person < Flexirest::Base
1015
- get :all, '/people?all=true' # THIS IS WRONG!!!
1016
- end
1017
- ```
1018
-
1019
- You should use a defaults option to specify the defaults, then they will be correctly overwritten when making the request
1020
-
1021
- ```ruby
1022
- class Person < Flexirest::Base
1023
- get :all, '/people', :defaults => {:active => true}
1024
- end
1025
-
1026
- @people = Person.all(active:false)
1027
- ```
1028
-
1029
- If you specify `defaults` as a `Proc` this will be executed with the set parameters (which you can change). For example to allow you to specify a reference (but the API wants it formated as "id-reference") you could use:
1030
-
1031
- ```ruby
1032
- class Person < Flexirest::Base
1033
- get :all, "/", defaults: (Proc.new do |params|
1034
- reference = params.delete(:reference) # Delete the old parameter
1035
- {
1036
- id: "id-#{reference}"
1037
- } # The last thing is the hash of defaults
1038
- end)
1039
- end
1040
- ```
1041
-
1042
- ### Root elements
1043
-
1044
- If your JSON or XML object comes back with a root node and you'd like to ignore it, you can define the mapping as:
1045
-
1046
- ```ruby
1047
- class Feed < Flexirest::Base
1048
- post :list, "/feed", ignore_root: "feed"
1049
- end
1050
- ```
1051
-
1052
- Alternatively if you want to wrap your JSON request body in a root element, e.g.:
1053
-
1054
- ```json
1055
- {
1056
- "feed": {
1057
- "id": 1
1058
- }
1059
- }
1060
- ```
1061
-
1062
- You can do it like this:
1063
-
1064
- ```ruby
1065
- class Feed < Flexirest::Base
1066
- post :list, "/feed", wrap_root: "feed"
1067
- end
1068
-
1069
- Feed.list(id: 1)
1070
- ```
1071
-
1072
- ### Required Parameters
1073
-
1074
- If you want to specify that certain parameters are required for a specific call, you can specify them like:
1075
-
1076
- ```ruby
1077
- class Person < Flexirest::Base
1078
- get :all, '/people', :requires => [:active]
1079
- end
1080
-
1081
- @people = Person.all # raises Flexirest::MissingParametersException
1082
- @people = Person.all(active:false)
1083
- ```
1084
-
1085
- ### Updating Only Changed/Dirty
1086
-
1087
- The most common RESTful usage of the PATCH http-method is to only send fields that have changed. The default action for all calls is to send all known object attributes for POST/PUT/PATCH calls, but this can be altered by setting the `only_changed` option on your call declaration.
1088
-
1089
- ```ruby
1090
- class Person < Flexirest::Base
1091
- get :all, '/people'
1092
- patch :update, '/people/:id', :only_changed => true # only send attributes that are changed/dirty
1093
- end
1094
-
1095
- person = Person.all.first
1096
- person.first_name = 'Billy'
1097
- person.update # performs a PATCH request, sending only the now changed 'first_name' attribute
1098
- ```
1099
-
1100
- This functionality is per-call, and there is some additional flexibility to control which attributes are sent and when.
1101
-
1102
- ```ruby
1103
- class Person < Flexirest::Base
1104
- get :all, '/people'
1105
- patch :update_1, '/people/:id', :only_changed => true # only send attributes that are changed/dirty (all known attributes on this object are subject to evaluation)
1106
- patch :update_2, "/people/:id", :only_changed => [:first_name, :last_name, :dob] # only send these listed attributes, and only if they are changed/dirty
1107
- patch :update_3, "/people/:id", :only_changed => {first_name: true, last_name: true, dob: false} # include the listed attributes marked 'true' only when changed; attributes marked 'false' are always included (changed or not, and if not present will be sent as nil); unspecified attributes are never sent
1108
- end
1109
- ```
1110
-
1111
- #### Additional Notes:
1112
-
1113
- - The above examples specifically showed PATCH methods, but this is also available for POST and PUT methods for flexibility purposes (even though they break typical REST methodology).
1114
- - This logic is currently evaluated before Required Parameters, so it is possible to ensure that requirements are met by some clever usage.
1115
-
1116
- - This means that if a method is `:requires => [:active], :only_changed => {active: false}` then `active` will always have a value and would always pass the `:requires` directive (so you need to be very careful because the answer may end up being `nil` if you didn't specifically set it).
1117
-
1118
- ### HTTP/Parse Error Handling
1119
-
1120
- Sometimes the backend server may respond with a non-200/304 header, in which case the code will raise an `Flexirest::HTTPClientException` for 4xx errors or an `Flexirest::HTTPServerException` for 5xx errors. These both have a `status` accessor and a `result` accessor (for getting access to the parsed body):
1121
-
1122
- ```ruby
1123
- begin
1124
- Person.all
1125
- rescue Flexirest::HTTPClientException, Flexirest::HTTPServerException => e
1126
- Rails.logger.error("API returned #{e.status} : #{e.result.message}")
1127
- end
1128
- ```
1129
-
1130
- If the response is unparsable (e.g. not in the desired content type), then it will raise an `Flexirest::ResponseParseException` which has a `status` accessor for the HTTP status code and a `body` accessor for the unparsed response body.
1131
-
1132
- ### Validation
1133
-
1134
- You can create validations on your objects just like Rails' built in ActiveModel validations. For example:
1135
-
1136
- ```ruby
1137
- class Person < Flexirest::Base
1138
- validates :first_name, presence: true #ensures that the value is present and not blank
1139
- validates :last_name, existence: true #ensures that the value is non-nil only
1140
- validates :password, length: {within:6..12}, message: "Invalid password length, must be 6-12 characters"
1141
- validates :post_code, length: {minimum:6, maximum:8}
1142
- validates :salary, numericality: true, minimum: 20_000, maximum: 50_000
1143
- validates :age, numericality: { minumum: 18, maximum: 65 }
1144
- validates :suffix, inclusion: { in: %w{Dr. Mr. Mrs. Ms.}}
1145
-
1146
- validates :first_name do |object, name, value|
1147
- object._errors[name] << "must be over 4 chars long" if value.length <= 4
1148
- end
1149
-
1150
- get :index, '/'
1151
- end
1152
- ```
1153
-
1154
- Note: the block based validation is responsible for adding errors to `object._errors[name]` (and this will automatically be ready for `<<` inserting into).
1155
-
1156
- 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).
1157
-
1158
- `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:
1159
-
1160
- ```ruby
1161
- # ActiveRecord
1162
- validates :name, presence: { message: "must be given please" }
1163
-
1164
- # Flexirest
1165
- validates :name, :presence, message: "must be given please"
1166
- ```
1167
-
1168
- #### Permitting nil values
1169
-
1170
- 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:
1171
-
1172
- ```ruby
1173
- class Person < Flexirest::Base
1174
- validates :first_name, presence: true
1175
- validates :middle_name, length: { minimum: 2, maximum: 30 }, allow_nil: true
1176
- validates :last_name, existence: true
1177
- validates :nick_name, length: { minimum: 2, maximum: 30 }
1178
- validates :alias, length: { minimum: 2, maximum: 30 }, allow_nil: false
1179
- validates :password, length: { within: 6..12 }
1180
- validates :post_code, length: { minimum: 6, maximum: 8 }
1181
- validates :salary, numericality: true, minimum: 20_000, maximum: 50_000
1182
- validates :age, numericality: { minimum: 18, maximum: 65 }
1183
- validates :suffix, inclusion: { in: %w{Dr. Mr. Mrs. Ms.}}
1184
- validates :golf_score, numericality: true, allow_nil: true
1185
- validates :retirement_age, numericality: { minimum: 65 }, allow_nil: true
1186
- validates :cars_owned, numericality: true
1187
- validates :houses_owned, numericality: true, allow_nil: false
1188
- validates :favorite_authors, inclusion: { in: ["George S. Klason", "Robert T. Kiyosaki", "Lee Child"] }, allow_nil: true
1189
- validates :favorite_artists, inclusion: { in: ["Claude Monet", "Vincent Van Gogh", "Andy Warhol"] }
1190
- validates :favorite_composers, inclusion: { in: ["Mozart", "Bach", "Pachelbel", "Beethoven"] }, allow_nil: false
1191
- end
1192
- ```
1193
-
1194
- In the example above, the following results would occur when calling `valid?` on an instance where all attributes have `nil` values:
1195
-
1196
- - `:first_name` must be present
1197
- - `:last_name` must be not be nil
1198
- - `:nick_name` must be not be nil
1199
- - `:alias` must not be nil
1200
- - `:password` must not be nil
1201
- - `:post_code` must not be nil
1202
- - `:salary` must not be nil
1203
- - `:age` must not be nil
1204
- - `:suffix` must not be nil
1205
- - `:cars_owned` must not be nil
1206
- - `:houses_owned` must not be nil
1207
- - `:favorite_artists` must not be nil
1208
- - `:favorite_composers` must not be nil
1209
-
1210
- The following attributes will pass validation since they explicitly `allow_nil`:
1211
-
1212
- - `:middle_name`
1213
- - `:golf_score`
1214
- - `:retirement_age`
1215
- - `:favorite_authors`
1216
-
1217
- ### Filtering result lists
1218
-
1219
- 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):
1220
-
1221
- ```ruby
1222
- class Article < Flexirest::Base
1223
- get :all, "/articles"
1224
- end
1225
-
1226
- Article.all.where(published: true, department: /technical\-/)
1227
- ```
1228
-
1229
- ## Debugging
1230
-
1231
- You can turn on verbose debugging to see what is sent to the API server and what is returned in one of these two ways:
1232
-
1233
- ```ruby
1234
- class Article < Flexirest::Base
1235
- verbose true
1236
- end
1237
-
1238
- class Person < Flexirest::Base
1239
- verbose!
1240
- end
1241
- ```
1242
-
1243
- By default verbose logging isn't enabled, so it's up to the developer to enable it (and remember to disable it afterwards). It does use debug level logging, so it shouldn't fill up a correctly configured production server anyway.
1244
-
1245
- If you prefer to record the output of an API call in a more automated fashion you can use a callback called `record_response` like this:
1246
-
1247
- ```ruby
1248
- class Article < Flexirest::Base
1249
- record_response do |url, response|
1250
- File.open(url.parameterize, "w") do |f|
1251
- f << response.body
1252
- end
1253
- end
1254
- end
1255
- ```
1256
-
1257
- ## XML Responses
1258
-
1259
- 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`):
1260
-
1261
- ```xml
1262
- <?xml version="1.0" encoding="utf-8"?>
1263
- <feed xmlns="http://www.w3.org/2005/Atom">
1264
-
1265
- <title>Example Feed</title>
1266
- <link href="http://example.org/"/>
1267
- <updated>2003-12-13T18:30:02Z</updated>
1268
- <author>
1269
- <name>John Doe</name>
1270
- </author>
1271
- <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
1272
-
1273
- <entry>
1274
- <title>Atom-Powered Robots Run Amok</title>
1275
- <link href="http://example.org/2003/12/13/atom03"/>
1276
- <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
1277
- <updated>2003-12-13T18:30:02Z</updated>
1278
- <summary>Some text.</summary>
1279
- </entry>
1280
-
1281
- <entry>
1282
- <title>Something else cool happened</title>
1283
- <link href="http://example.org/2015/08/11/andyjeffries"/>
1284
- <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6b</id>
1285
- <updated>2015-08-11T18:30:02Z</updated>
1286
- <summary>Some other text.</summary>
1287
- </entry>
1288
-
1289
- </feed>
1290
- ```
1291
-
1292
- You can use:
1293
-
1294
- ```ruby
1295
- class Feed < Flexirest::Base
1296
- base_url "http://www.example.com/v1/"
1297
- get :atom, "/atom"
1298
- end
1299
-
1300
- @atom = Feed.atom
1301
-
1302
- puts @atom.feed.title
1303
- puts @atom.feed.link.href
1304
- @atom.feed.entry.each do |entry|
1305
- puts "#{entry.title} -> #{entry.link.href}"
1306
- end
1307
- ```
1308
-
1309
- 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.
1310
-
1311
- ## Contributing
1312
-
1313
- 1. Fork it
1314
- 2. Create your feature branch (`git checkout -b my-new-feature`)
1315
- 3. Commit your changes (`git commit -am 'Add some feature'`)
1316
- 4. Push to the branch (`git push origin my-new-feature`)
1317
- 5. Create new Pull Request
166
+ We have a guide written about [Flexirest Internals](docs/internals.md) if you want to know how it all hangs together. We also have lots of RSpec specifications, tested with Travis CI.