active_rest_client 0.9.58

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.simplecov +4 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +9 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +585 -0
  9. data/Rakefile +3 -0
  10. data/active_rest_client.gemspec +34 -0
  11. data/lib/active_rest_client.rb +23 -0
  12. data/lib/active_rest_client/base.rb +128 -0
  13. data/lib/active_rest_client/caching.rb +84 -0
  14. data/lib/active_rest_client/configuration.rb +69 -0
  15. data/lib/active_rest_client/connection.rb +76 -0
  16. data/lib/active_rest_client/connection_manager.rb +21 -0
  17. data/lib/active_rest_client/headers_list.rb +47 -0
  18. data/lib/active_rest_client/instrumentation.rb +62 -0
  19. data/lib/active_rest_client/lazy_association_loader.rb +95 -0
  20. data/lib/active_rest_client/lazy_loader.rb +23 -0
  21. data/lib/active_rest_client/logger.rb +67 -0
  22. data/lib/active_rest_client/mapping.rb +65 -0
  23. data/lib/active_rest_client/proxy_base.rb +143 -0
  24. data/lib/active_rest_client/recording.rb +24 -0
  25. data/lib/active_rest_client/request.rb +412 -0
  26. data/lib/active_rest_client/request_filtering.rb +52 -0
  27. data/lib/active_rest_client/result_iterator.rb +66 -0
  28. data/lib/active_rest_client/validation.rb +60 -0
  29. data/lib/active_rest_client/version.rb +3 -0
  30. data/spec/lib/base_spec.rb +245 -0
  31. data/spec/lib/caching_spec.rb +179 -0
  32. data/spec/lib/configuration_spec.rb +105 -0
  33. data/spec/lib/connection_manager_spec.rb +36 -0
  34. data/spec/lib/connection_spec.rb +73 -0
  35. data/spec/lib/headers_list_spec.rb +61 -0
  36. data/spec/lib/instrumentation_spec.rb +59 -0
  37. data/spec/lib/lazy_association_loader_spec.rb +118 -0
  38. data/spec/lib/lazy_loader_spec.rb +25 -0
  39. data/spec/lib/logger_spec.rb +63 -0
  40. data/spec/lib/mapping_spec.rb +48 -0
  41. data/spec/lib/proxy_spec.rb +154 -0
  42. data/spec/lib/recording_spec.rb +34 -0
  43. data/spec/lib/request_filtering_spec.rb +72 -0
  44. data/spec/lib/request_spec.rb +471 -0
  45. data/spec/lib/result_iterator_spec.rb +104 -0
  46. data/spec/lib/validation_spec.rb +113 -0
  47. data/spec/spec_helper.rb +22 -0
  48. metadata +265 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ac09a55ea1ed10c3bc4d42e8169c0a628eab30be
4
+ data.tar.gz: b78ebbfa5f5c2b13344f99a77c7bed636ec91e59
5
+ SHA512:
6
+ metadata.gz: 693b51536a3cc5b13a21955eb777b8d3eb1dbcb30d45844c0dd5481ed1bff8832fe9b1660b125286b8a6e6eb55f7fec6c86bf8765f736bac7a0383c3ecfd7436
7
+ data.tar.gz: 5915860315ae8d13bdc83a956ddbeb10b49b8edd41ed664ac8ef8b395f4dd3fad35730e870c532bf41bc09730dcfb26fcba18725f85a354697221c7e1bc26de5
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.simplecov ADDED
@@ -0,0 +1,4 @@
1
+ SimpleCov.start do
2
+ minimum_coverage 80
3
+ add_filter '/spec/'
4
+ end
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in active_rest_client.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec do
5
+ watch(%r{^spec/.*/*_spec\.rb$}) { "spec" }
6
+ watch(%r{^lib/.*/(.+)\.rb$}) { "spec" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
9
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Which? Ltd
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,585 @@
1
+ # ActiveRestClient
2
+
3
+ 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.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'active_rest_client'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install active_rest_client
20
+
21
+ ## Usage
22
+
23
+ First you need to create your new model class:
24
+
25
+ ```ruby
26
+ # config/environments/production.rb
27
+ MyApp::Application.configure do
28
+ # ...
29
+ config.api_server_url = "https://www.example.com/api/v1"
30
+ end
31
+
32
+ # app/models/person.rb
33
+ class Person < ActiveRestClient::Base
34
+ base_url Rails.application.config.api_server_url
35
+
36
+ get :all, "/people"
37
+ get :find, "/people/:id"
38
+ put :save, "/people/:id"
39
+ post :create, "/people"
40
+ end
41
+ ```
42
+
43
+ Note I've specified the base_url in the class above. This is usful 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 the application.rb/production.rb:
44
+
45
+ ```ruby
46
+ ActiveRestClient::Base.base_url = "https://www.example.com/api/v1"
47
+ ```
48
+
49
+ Any `base_url` settings in specific classes override this declared default. You can then use your new class like this:
50
+
51
+ ```ruby
52
+ # Create a new person
53
+ @person = Person.create(
54
+ first_name:"John"
55
+ last_name:"Smith"
56
+ )
57
+
58
+ # Find a person (not needed after creating)
59
+ id = @person.id
60
+ @person = Person.find(id)
61
+
62
+ # Update a person
63
+ @person.last_name = "Jones"
64
+ @person.save
65
+
66
+ # Get all people
67
+ @people = Person.all
68
+ @people.each do |person|
69
+ puts "Hi " + person.first_name
70
+ end
71
+ ```
72
+
73
+ 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.
74
+
75
+ ```ruby
76
+ @person = Person.find(1234) # valid
77
+ @person = Person.find("1234") # valid
78
+ @person = Person.find(:id => 1234) # valid
79
+ @person = Person.find(:id => 1234, :name => "Billy") # valid
80
+ @person = Person.find(1234, :name => "Billy") # invalid
81
+ ```
82
+
83
+ 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:
84
+
85
+ ```ruby
86
+ @person = Person.new
87
+ @person.first_name = "John"
88
+ @person.last_name = "Smith"
89
+ @person.create
90
+ puts @person.id
91
+ ```
92
+
93
+ The response of the #create call set the attributes at that point (any manually set attributes before that point are removed).
94
+
95
+ 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:
96
+
97
+ ```ruby
98
+ @tv = Tv.find(model:"UE55U8000") # { "properties" : {"3d" : false} }
99
+ puts @tv.properties["3d"]
100
+ @tv.properties["3d"] = true
101
+ ```
102
+
103
+ ## Advanced Features
104
+
105
+ ### Associations
106
+
107
+ 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.
108
+
109
+ #### Association Type 1 - Loading Other Classes
110
+
111
+ If the call would return a list of instances that should be considered another object, you can also specify this when mapping the method using the `:has_many` option. 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.
112
+
113
+ ```ruby
114
+ class Expense < ActiveRestClient::Base
115
+ def inc_vat
116
+ ex_vat * 1.20
117
+ end
118
+ end
119
+
120
+ class Person < ActiveRestClient::Base
121
+ get :find, "/people/:id", :has_many => {:expenses => Expense}
122
+ end
123
+
124
+ @person = Person.find(1)
125
+ puts @person.expenses.reduce {|e| e.inc_vat}
126
+ ```
127
+
128
+ #### Association Type 2 - Lazy Loading From Other URLs
129
+
130
+ 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:
131
+
132
+ ```ruby
133
+ "attribute" : "URL"
134
+ "attribute" : ["URL", "URL"]
135
+ "attribute" : { "url" : "URL"}
136
+ "attribute" : { "href" : "URL"}
137
+ "attribute" : { "something" : "URL"}
138
+ ```
139
+
140
+ 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).
141
+
142
+ It is required that the URL is a complete URL including a protocol starting with "http". To configure this use code like:
143
+
144
+ ```ruby
145
+ class Person < ActiveRestClient::Base
146
+ get :find, "/people/:id", :lazy => [:orders, :refunds]
147
+ end
148
+ ```
149
+
150
+ And use it like this:
151
+
152
+ ```ruby
153
+ # Makes a call to /people/1
154
+ @person = Person.find(1)
155
+
156
+ # Makes a call to the first URL found in the "books":[...] array in the article response
157
+ # only makes the HTTP request when first used though
158
+ @person.books.first.name
159
+ ```
160
+
161
+ #### Association Type 3 - HAL Auto-loaded Resources
162
+
163
+ 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.
164
+
165
+ 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).
166
+
167
+ ```ruby
168
+ @person = Person.find(1)
169
+ @person.students[0]._hal_attributes("title")
170
+ ```
171
+
172
+ #### Combined Example
173
+
174
+ 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:
175
+
176
+ ```ruby
177
+ class Article < ActiveRestClient::Base
178
+ get :find, '/articles/:id', has_many:{:images => Image} # ,lazy:[:images] isn't needed as we're using HAL
179
+ end
180
+
181
+ class Image < ActiveRestClient::Base
182
+ # You may have mappings here
183
+
184
+ def nice_size
185
+ "#{size/1024}KB"
186
+ end
187
+ end
188
+ ```
189
+
190
+ We assume the /articles/:id call returns something like the following:
191
+
192
+ ```json
193
+ {
194
+ "title": "Fly Fishing",
195
+ "author": "J R Hartley",
196
+ "images": [
197
+ "http://api.example.com/images/1",
198
+ "http://api.example.com/images/2"
199
+ ]
200
+ }
201
+ ```
202
+
203
+ We said above that the /images/:id call would return something like:
204
+
205
+ ```json
206
+ {
207
+ "filename": "http://cdn.example.com/images/foo.jpg",
208
+ "filesize": 123456
209
+ }
210
+ ```
211
+
212
+ When it comes time to use it, you would do something like this:
213
+
214
+ ```ruby
215
+ @article = Article.find(1)
216
+ @article.images.is_a?(ActiveRestClient::LazyAssociationLoader)
217
+ @article.images.size == 2
218
+ @article.images.each do |image|
219
+ puts image.inspect
220
+ end
221
+ ```
222
+
223
+ 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:
224
+
225
+ ```ruby
226
+ @image = @article.images.first
227
+ puts @image.filename
228
+ # => http://cdn.example.com/images/foo.jpg
229
+ puts @image.filesize
230
+ # => 123456
231
+ ```
232
+
233
+ 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.
234
+
235
+ ```ruby
236
+ puts @image.nice_size
237
+ # => 121KB
238
+ ```
239
+
240
+ ### Caching
241
+
242
+ Expires and ETag based caching is enabled by default, but with a simple line in the application.rb/production.rb you can disable it:
243
+
244
+ ```ruby
245
+ ActiveRestClient::Base.perform_caching = false
246
+ ```
247
+
248
+ or you can disable it per classes with:
249
+
250
+ ```ruby
251
+ class Person < ActiveRestClient::Base
252
+ perform_caching false
253
+ end
254
+ ```
255
+
256
+ 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:
257
+
258
+ ```ruby
259
+ ActiveRestClient::Base.cache_store = Redis::Store.new("redis://localhost:6379/0/cache")
260
+ ```
261
+
262
+ ### Using filters
263
+
264
+ You can use filters 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. This can either be a block or a named method (like ActionController's `before_filter`/`before_action` methods).
265
+
266
+ The filter is passed the name of the method (e.g. `:save`) and a request object. 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)
267
+
268
+ ```ruby
269
+ require 'secure_random'
270
+
271
+ class Person < ActiveRestClient::Base
272
+ before_request do |name, request|
273
+ if request.post? || name == :save
274
+ id = request.post_params.delete(:id)
275
+ request.get_params[:id] = id
276
+ end
277
+ end
278
+
279
+ before_request :replace_token_in_url
280
+
281
+ before_request :add_authentication_details
282
+
283
+ before_request :replace_body
284
+
285
+ private
286
+
287
+ def replace_token_in_url(name, request)
288
+ request.url.gsub!("#token", SecureRandom.hex)
289
+ end
290
+
291
+ def add_authentication_details(name, request)
292
+ request.headers["X-Custom-Authentication-Token"] = ENV["AUTH_TOKEN"]
293
+ end
294
+
295
+ def replace_body(name, request)
296
+ if name == :create
297
+ request.body = request.post_params.to_json
298
+ end
299
+ end
300
+ end
301
+ ```
302
+
303
+ If you need to, you can create a custom parent class with a `before_request` filter and all children will inherit this filter.
304
+
305
+ ```ruby
306
+ class MyProject::Base < ActiveRestClient::Base
307
+ before_request do |name, request|
308
+ request.get_params[:api_key] = "1234567890-1234567890"
309
+ end
310
+ end
311
+
312
+ class Person < MyProject::Base
313
+ # No need to declare a before_request for :api_key, already defined by the parent
314
+ end
315
+ ```
316
+
317
+ ### Lazy Loading
318
+
319
+ ActiveRestClient 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).
320
+
321
+ **Note: Currently this isn't enabled by default, but this is likely to change in the future to make lazy loading the default.**
322
+
323
+ To enable it, simply call the lazy_load! method in your class definition:
324
+
325
+ ```ruby
326
+ class Article < ActiveRestClient::Base
327
+ lazy_load!
328
+ end
329
+ ```
330
+
331
+ 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:
332
+
333
+ ```ruby
334
+ items.parallelise(:id)
335
+
336
+ # or
337
+
338
+ items.parallelise do |item|
339
+ item.id
340
+ end
341
+ ```
342
+
343
+ 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.
344
+
345
+
346
+ ### Authentication
347
+
348
+ 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:
349
+
350
+ ```ruby
351
+ class Person < ActiveRestClient::Base
352
+ username 'api'
353
+ password 'eb693ec-8252c-d6301-02fd0-d0fb7-c3485'
354
+
355
+ # ...
356
+ end
357
+ ```
358
+
359
+ ### Faking Calls
360
+
361
+ There are times when an API hasn't been developed yet, so you want to fake the API call response. To do this, simply pass a `fake` option when mapping the call containing the response.
362
+
363
+ ```ruby
364
+ class Person < ActiveRestClient::Base
365
+ get :all, '/people', :fake => "[{first_name:"Johnny"}, {first_name:"Bob"}]"
366
+ end
367
+ ```
368
+
369
+ ### Raw Requests
370
+
371
+ 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 filters run (say for authentication). The easiest way to do that is to call `_request` on the class:
372
+
373
+ ```ruby
374
+ class Person < ActiveRestClient::Base
375
+ end
376
+
377
+ people = Person._request('http://api.example.com/v1/people') # Defaults to get with no parameters
378
+ # people is a normal ActiveRestClient object, implementing iteration, HAL loading, etc.
379
+
380
+ Person._request('http://api.example.com/v1/people', :post, {id:1234,name:"John"}) # Post with parameters
381
+ ```
382
+
383
+ 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):
384
+
385
+ ```ruby
386
+ @person = Person._lazy_request(Person._request_for(:find, 1234))
387
+ ```
388
+
389
+ This initially creates an ActiveRestClient::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:
390
+
391
+ ```ruby
392
+ @person = Person.lazy_find(1234)
393
+ ```
394
+
395
+ 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.
396
+
397
+ ### Proxying APIs
398
+
399
+ 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. In this case you can define a descendent of `ActiveRestClient::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:
400
+
401
+ ```ruby
402
+ class ArticleProxy < ActiveRestClient::ProxyBase
403
+ get "/all" do
404
+ url "/all_people" # Equiv to url.gsub!("/all", "/all_people") if you wanted to keep params
405
+ response = passthrough
406
+ translate(response) do |body|
407
+ body["first_name"] = body.delete("fname")
408
+ body
409
+ end
410
+ end
411
+ end
412
+
413
+ class Article < ActiveRestClient::Base
414
+ proxy ArticleProxy
415
+ base_url "http://www.example.com"
416
+
417
+ get :all, "/all", fake:"{\"name\":\"Billy\"}"
418
+ get :list, "/list", fake:"[{\"name\":\"Billy\"}, {\"name\":\"John\"}]"
419
+ end
420
+
421
+ Article.all.first_name == "Billy"
422
+ ```
423
+
424
+ This example does two things:
425
+
426
+ 1. It rewrites the incoming URL for any requests matching "*/all*" to "/all_people"
427
+ 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)
428
+
429
+ 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.
430
+
431
+ 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:
432
+
433
+ ```ruby
434
+ get "/list" do
435
+ get_params["id"] = get_params.delete("identifier")
436
+ passthrough
437
+ end
438
+ ```
439
+
440
+ 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.
441
+
442
+ If you want to manually set the body for the API yourself you can use the `body` method
443
+
444
+ ```ruby
445
+ put "/update" do
446
+ body "{\"id\":#{post_params["id"]}}"
447
+ passthrough
448
+ end
449
+ ```
450
+
451
+ This example takes the `post_params["id"]` and converts the body from being a normal form-encoded body in to being a JSON body.
452
+
453
+ The proxy block expects one of three things to be the return value of the block.
454
+
455
+ 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
456
+ 2. The second option is to save the response from `passthrough` and use `translate` on it to alter the structure.
457
+ 3. The third option is to use `render` if you want to completely fake an API and return the JSON yourself
458
+
459
+ 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.
460
+
461
+ ```ruby
462
+ put "/fake" do
463
+ render "{\"id\":1234}"
464
+ end
465
+ ```
466
+
467
+ ### Translating APIs
468
+
469
+ **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.**
470
+
471
+ 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:
472
+
473
+ ```ruby
474
+ class ArticleTranslator
475
+ def self.all(object)
476
+ ret = {}
477
+ ret["first_name"] = object["name"]
478
+ ret
479
+ end
480
+ end
481
+
482
+ class Article < ActiveRestClient::Base
483
+ translator ArticleTranslator
484
+ base_url "http://www.example.com"
485
+
486
+ get :all, "/all", fake:"{\"name\":\"Billy\"}"
487
+ get :list, "/list", fake:"[{\"name\":\"Billy\"}, {\"name\":\"John\"}]"
488
+ end
489
+
490
+ Article.all.first_name == "Billy"
491
+ ```
492
+
493
+ ### Default Parameters
494
+
495
+ If you want to specify default parameters you shouldn't use a path like:
496
+
497
+ ```ruby
498
+ class Person < ActiveRestClient::Base
499
+ get :all, '/people?all=true' # THIS IS WRONG!!!
500
+ end
501
+ ```
502
+
503
+ You should use a defaults option to specify the defaults, then they will be correctly overwritten when making the request
504
+
505
+ ```ruby
506
+ class Person < ActiveRestClient::Base
507
+ get :all, '/people', :defaults => {:active => true}
508
+ end
509
+
510
+ @people = Person.all(active:false)
511
+ ```
512
+
513
+ ### HTTP/Parse Error Handling
514
+
515
+ Sometimes the backend server may respond with a non-200/304 header, in which case the code will raise an `ActiveRestClient::HTTPClientException` for 4xx errors or an `ActiveRestClient::HTTPServerException` for 5xx errors. These both have a `status` accessor and a `result` accessor (for getting access to the parsed body):
516
+
517
+ ```ruby
518
+ begin
519
+ Person.all
520
+ rescue ActiveRestClient::HTTPClientException, ActiveRestClient::HTTPServerException => e
521
+ Rails.logger.error("API returned #{e.status} : #{e.result.message}")
522
+ end
523
+ ```
524
+
525
+ If the response is unparsable (e.g. not in the desired content type), then it will raise an `ActiveRestClient::ResponseParseException` which has a `status` accessor for the HTTP status code and a `body` accessor for the unparsed response body.
526
+
527
+ ### Validation
528
+
529
+ You can create validations on your objects just like Rails' built in ActiveModel validations. For example:
530
+
531
+ ```ruby
532
+ class Person < ActiveRestClient::Base
533
+ validates :first_name, presence:true
534
+ validates :password, length:{within:6..12}
535
+ validates :post_code, length:{minimum:6, maximum:8}
536
+ validates :salary, numericality:true, minimum:20_000, maximum:50_000
537
+
538
+ validates :first_name do |object, name, value|
539
+ object.errors[name] << "must be over 4 chars long" if value.length <= 4
540
+ end
541
+
542
+ get :index, '/'
543
+ end
544
+ ```
545
+
546
+ Note the block based validation is responsible for adding errors to `object.errors[name]` (and this will automatically be ready for `<<` inserting into).
547
+
548
+ 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).
549
+
550
+ ### Debugging
551
+
552
+ 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:
553
+
554
+ ```ruby
555
+ class Article < ActiveRestClient::Base
556
+ verbose true
557
+ end
558
+
559
+ class Person < ActiveRestClient::Base
560
+ verbose!
561
+ end
562
+ ```
563
+
564
+ 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.
565
+
566
+ 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:
567
+
568
+ ```ruby
569
+ class Article < ActiveRestClient::Base
570
+ record_response do |url, response|
571
+ File.open(url.parameterize, "w") do |f|
572
+ f << response.body
573
+ end
574
+ end
575
+ end
576
+ ```
577
+
578
+
579
+ ## Contributing
580
+
581
+ 1. Fork it
582
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
583
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
584
+ 4. Push to the branch (`git push origin my-new-feature`)
585
+ 5. Create new Pull Request