carwow-json_api_client 1.19.0

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/LICENSE +20 -0
  3. data/README.md +706 -0
  4. data/Rakefile +32 -0
  5. data/lib/json_api_client.rb +30 -0
  6. data/lib/json_api_client/associations.rb +8 -0
  7. data/lib/json_api_client/associations/base_association.rb +33 -0
  8. data/lib/json_api_client/associations/belongs_to.rb +31 -0
  9. data/lib/json_api_client/associations/has_many.rb +8 -0
  10. data/lib/json_api_client/associations/has_one.rb +16 -0
  11. data/lib/json_api_client/connection.rb +41 -0
  12. data/lib/json_api_client/error_collector.rb +91 -0
  13. data/lib/json_api_client/errors.rb +107 -0
  14. data/lib/json_api_client/formatter.rb +145 -0
  15. data/lib/json_api_client/helpers.rb +9 -0
  16. data/lib/json_api_client/helpers/associatable.rb +88 -0
  17. data/lib/json_api_client/helpers/callbacks.rb +27 -0
  18. data/lib/json_api_client/helpers/dirty.rb +75 -0
  19. data/lib/json_api_client/helpers/dynamic_attributes.rb +78 -0
  20. data/lib/json_api_client/helpers/uri.rb +9 -0
  21. data/lib/json_api_client/implementation.rb +12 -0
  22. data/lib/json_api_client/included_data.rb +58 -0
  23. data/lib/json_api_client/linking.rb +6 -0
  24. data/lib/json_api_client/linking/links.rb +22 -0
  25. data/lib/json_api_client/linking/top_level_links.rb +39 -0
  26. data/lib/json_api_client/meta_data.rb +19 -0
  27. data/lib/json_api_client/middleware.rb +7 -0
  28. data/lib/json_api_client/middleware/json_request.rb +26 -0
  29. data/lib/json_api_client/middleware/parse_json.rb +31 -0
  30. data/lib/json_api_client/middleware/status.rb +67 -0
  31. data/lib/json_api_client/paginating.rb +6 -0
  32. data/lib/json_api_client/paginating/nested_param_paginator.rb +140 -0
  33. data/lib/json_api_client/paginating/paginator.rb +89 -0
  34. data/lib/json_api_client/parsers.rb +5 -0
  35. data/lib/json_api_client/parsers/parser.rb +102 -0
  36. data/lib/json_api_client/query.rb +6 -0
  37. data/lib/json_api_client/query/builder.rb +239 -0
  38. data/lib/json_api_client/query/requestor.rb +73 -0
  39. data/lib/json_api_client/relationships.rb +6 -0
  40. data/lib/json_api_client/relationships/relations.rb +55 -0
  41. data/lib/json_api_client/relationships/top_level_relations.rb +30 -0
  42. data/lib/json_api_client/request_params.rb +57 -0
  43. data/lib/json_api_client/resource.rb +643 -0
  44. data/lib/json_api_client/result_set.rb +25 -0
  45. data/lib/json_api_client/schema.rb +154 -0
  46. data/lib/json_api_client/utils.rb +48 -0
  47. data/lib/json_api_client/version.rb +3 -0
  48. metadata +213 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 69f261cc9207354c07f3ade7fe1ac13857c402d14db5aa649da2b12fc236d6a7
4
+ data.tar.gz: '085e02eedff3eeb712140844f92062341e6ff15736439efbd1f2caf4bccdb009'
5
+ SHA512:
6
+ metadata.gz: 766881cf48cb7906569430510f9e0f080994db74d7823879b6f61c1d3cbb85c611a6032752cf5a272721d45b35f924fc78b11695a9d40f6094f23aa23e10999b
7
+ data.tar.gz: 0ec1df1d83a28391093dd270cb4207142d5a41d16f90beb3c262967ec72ff93a638886ee23f1cf7360a9af7b8963c01e585dae713f863b5fcaf4134d6f7dedfe
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) {{year}} {{fullname}}
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,706 @@
1
+ # JsonApiClient [![Build Status](https://travis-ci.org/carwow/json_api_client.png?branch=master)](https://travis-ci.org/carwow/json_api_client) [![Code Climate](https://codeclimate.com/github/carwow/json_api_client.png)](https://codeclimate.com/github/carwow/json_api_client) [![Code Coverage](https://codeclimate.com/github/carwow/json_api_client/coverage.png)](https://codeclimate.com/github/carwow/json_api_client)
2
+
3
+ This gem is meant to help you build an API client for interacting with REST APIs as laid out by [http://jsonapi.org](http://jsonapi.org). It attempts to give you a query building framework that is easy to understand (it is similar to ActiveRecord scopes).
4
+
5
+ > NOTE: At carwow, we are committed to the open source community and our contributions will be published to the original gem and main repository as usual, but in the meantime we need to publish a new gem to carry on with our work.
6
+ > We are not the official maintainers for `json_api_client` gem, but we are happy to contribute to the OS community with this fork.
7
+
8
+ ## Usage
9
+
10
+ You will want to create your own resource classes that inherit from `JsonApiClient::Resource` similar to how you would create an `ActiveRecord` class. You may also want to create your own abstract base class to share common behavior. Additionally, you will probably want to namespace your models. Namespacing your model will not affect the url routing to that resource.
11
+
12
+ ```ruby
13
+ module MyApi
14
+ # this is an "abstract" base class that
15
+ class Base < JsonApiClient::Resource
16
+ # set the api base url in an abstract base class
17
+ self.site = "http://example.com/"
18
+ end
19
+
20
+ class Article < Base
21
+ end
22
+
23
+ class Comment < Base
24
+ end
25
+
26
+ class Person < Base
27
+ end
28
+ end
29
+ ```
30
+
31
+ By convention, we guess the resource route from the class name. In the above example, `Article`'s path is "http://example.com/articles" and `Person`'s path would be "http://example.com/people".
32
+
33
+ Some basic example usage:
34
+
35
+ ```ruby
36
+ MyApi::Article.all
37
+ MyApi::Article.where(author_id: 1).find(2)
38
+ MyApi::Article.where(author_id: 1).all
39
+
40
+ MyApi::Person.where(name: "foo").order(created_at: :desc).includes(:preferences, :cars).all
41
+
42
+ u = MyApi::Person.new(first_name: "bar", last_name: "foo")
43
+ u.new_record?
44
+ # => true
45
+ u.save
46
+
47
+ u.new_record?
48
+ # => false
49
+
50
+ u = MyApi::Person.find(1).first
51
+ u.update_attributes(
52
+ a: "b",
53
+ c: "d"
54
+ )
55
+
56
+ u.persisted?
57
+ # => true
58
+
59
+ u.destroy
60
+
61
+ u.destroyed?
62
+ # => true
63
+ u.persisted?
64
+ # => false
65
+
66
+ u = MyApi::Person.create(
67
+ a: "b",
68
+ c: "d"
69
+ )
70
+ ```
71
+
72
+ All class level finders/creators should return a `JsonApiClient::ResultSet` which behaves like an Array and contains extra data about the api response.
73
+
74
+
75
+ ## Handling Validation Errors
76
+
77
+ [See specification](http://jsonapi.org/format/#errors)
78
+
79
+ Out of the box, `json_api_client` handles server side validation only.
80
+
81
+ ```ruby
82
+ User.create(name: "Bob", email_address: "invalid email")
83
+ # => false
84
+
85
+ user = User.new(name: "Bob", email_address: "invalid email")
86
+ user.save
87
+ # => false
88
+
89
+ # returns an error collector which is array-like
90
+ user.errors
91
+ # => ["Email address is invalid"]
92
+
93
+ # get all error titles
94
+ user.errors.full_messages
95
+ # => ["Email address is invalid"]
96
+
97
+ # get errors for a specific parameter
98
+ user.errors[:email_address]
99
+ # => ["Email address is invalid"]
100
+
101
+ user = User.find(1)
102
+ user.update_attributes(email_address: "invalid email")
103
+ # => false
104
+
105
+ user.errors
106
+ # => ["Email address is invalid"]
107
+
108
+ user.email_address
109
+ # => "invalid email"
110
+ ```
111
+
112
+ For now we are assuming that error sources are all parameters.
113
+
114
+ If you want to add client side validation, I suggest creating a form model class that uses ActiveModel's validations.
115
+
116
+ ## Meta information
117
+
118
+ [See specification](http://jsonapi.org/format/#document-structure-meta)
119
+
120
+ If the response has a top level meta data section, we can access it via the `meta` accessor on `ResultSet`.
121
+
122
+ ```ruby
123
+ # Example response:
124
+ {
125
+ "meta": {
126
+ "copyright": "Copyright 2015 Example Corp.",
127
+ "authors": [
128
+ "Yehuda Katz",
129
+ "Steve Klabnik",
130
+ "Dan Gebhardt"
131
+ ]
132
+ },
133
+ "data": {
134
+ // ...
135
+ }
136
+ }
137
+ articles = Articles.all
138
+
139
+ articles.meta.copyright
140
+ # => "Copyright 2015 Example Corp."
141
+
142
+ articles.meta.authors
143
+ # => ["Yehuda Katz", "Steve Klabnik", "Dan Gebhardt"]
144
+ ```
145
+
146
+ ## Top-level Links
147
+
148
+ [See specification](http://jsonapi.org/format/#document-structure-top-level-links)
149
+
150
+ If the resource returns top level links, we can access them via the `links` accessor on `ResultSet`.
151
+
152
+ ```ruby
153
+ articles = Articles.find(1)
154
+ articles.links.related
155
+ ```
156
+
157
+ ## Nested Resources
158
+
159
+ You can force nested resource paths for your models by using a `belongs_to` association.
160
+
161
+ **Note: Using belongs_to is only necessary for setting a nested path unless you provide `shallow_path: true` option.**
162
+
163
+ ```ruby
164
+ module MyApi
165
+ class Account < JsonApiClient::Resource
166
+ belongs_to :user
167
+ end
168
+
169
+ class Customer < JsonApiClient::Resource
170
+ belongs_to :user, shallow_path: true
171
+ end
172
+ end
173
+
174
+ # try to find without the nested parameter
175
+ MyApi::Account.find(1)
176
+ # => raises ArgumentError
177
+
178
+ # makes request to /users/2/accounts/1
179
+ MyApi::Account.where(user_id: 2).find(1)
180
+ # => returns ResultSet
181
+
182
+ # makes request to /customers/1
183
+ MyApi::Customer.find(1)
184
+ # => returns ResultSet
185
+
186
+ # makes request to /users/2/customers/1
187
+ MyApi::Customer.where(user_id: 2).find(1)
188
+ # => returns ResultSet
189
+ ```
190
+
191
+ you can also override param name for `belongs_to` association
192
+
193
+ ```ruby
194
+ module MyApi
195
+ class Account < JsonApiClient::Resource
196
+ belongs_to :user, param: :customer_id
197
+ end
198
+ end
199
+
200
+ # makes request to /users/2/accounts/1
201
+ MyApi::Account.where(customer_id: 2).find(1)
202
+ # => returns ResultSet
203
+ ```
204
+
205
+ ## Custom Methods
206
+
207
+ You can create custom methods on both collections (class method) and members (instance methods).
208
+
209
+ ```ruby
210
+ module MyApi
211
+ class User < JsonApiClient::Resource
212
+ # GET /users/search
213
+ custom_endpoint :search, on: :collection, request_method: :get
214
+
215
+ # PUT /users/:id/verify
216
+ custom_endpoint :verify, on: :member, request_method: :put
217
+ end
218
+ end
219
+
220
+ # makes GET request to /users/search?name=Jeff
221
+ MyApi::User.search(name: 'Jeff')
222
+ # => <ResultSet of MyApi::User instances>
223
+
224
+ user = MyApi::User.find(1)
225
+ # makes PUT request to /users/1/verify?foo=bar
226
+ user.verify(foo: 'bar')
227
+ ```
228
+
229
+ ## Fetching Includes
230
+
231
+ [See specification](http://jsonapi.org/format/#fetching-includes)
232
+
233
+ If the response returns a [compound document](http://jsonapi.org/format/#document-compound-documents), then we should be able to get the related resources.
234
+
235
+ ```ruby
236
+ # makes request to /articles/1?include=author,comments.author
237
+ results = Article.includes(:author, :comments => :author).find(1)
238
+
239
+ # should not have to make additional requests to the server
240
+ authors = results.map(&:author)
241
+
242
+ # makes POST request to /articles?include=author,comments.author
243
+ article = Article.new(title: 'New one').request_includes(:author, :comments => :author)
244
+ article.save
245
+
246
+ # makes PATCH request to /articles/1?include=author,comments.author
247
+ article = Article.find(1)
248
+ article.title = 'Changed'
249
+ article.request_includes(:author, :comments => :author)
250
+ article.save
251
+
252
+ # request includes will be cleared if response is successful
253
+ # to avoid this `keep_request_params` class attribute can be used
254
+ Article.keep_request_params = true
255
+
256
+ # to clear request_includes use
257
+ article.reset_request_includes!
258
+ ```
259
+
260
+ ## Sparse Fieldsets
261
+
262
+ [See specification](http://jsonapi.org/format/#fetching-sparse-fieldsets)
263
+
264
+ ```ruby
265
+ # makes request to /articles?fields[articles]=title,body
266
+ article = Article.select("title", "body").first
267
+
268
+ # should have fetched the requested fields
269
+ article.title
270
+ # => "Rails is Omakase"
271
+
272
+ # should not have returned the created_at
273
+ article.created_at
274
+ # => raise NoMethodError
275
+
276
+ # or you can use fieldsets from multiple resources
277
+ # makes request to /articles?fields[articles]=title,body&fields[comments]=tag
278
+ article = Article.select("title", "body",{comments: 'tag'}).first
279
+
280
+ # makes POST request to /articles?fields[articles]=title,body&fields[comments]=tag
281
+ article = Article.new(title: 'New one').request_select(:title, :body, comments: 'tag')
282
+ article.save
283
+
284
+ # makes PATCH request to /articles/1?fields[articles]=title,body&fields[comments]=tag
285
+ article = Article.find(1)
286
+ article.title = 'Changed'
287
+ article.request_select(:title, :body, comments: 'tag')
288
+ article.save
289
+
290
+ # request fields will be cleared if response is successful
291
+ # to avoid this `keep_request_params` class attribute can be used
292
+ Article.keep_request_params = true
293
+
294
+ # to clear request fields use
295
+ article.reset_request_select!(:comments) # to clear for comments
296
+ article.reset_request_select! # to clear for all fields
297
+ ```
298
+
299
+ ## Sorting
300
+
301
+ [See specification](http://jsonapi.org/format/#fetching-sorting)
302
+
303
+ ```ruby
304
+ # makes request to /people?sort=age
305
+ youngest = Person.order(:age).all
306
+
307
+ # also makes request to /people?sort=age
308
+ youngest = Person.order(age: :asc).all
309
+
310
+ # makes request to /people?sort=-age
311
+ oldest = Person.order(age: :desc).all
312
+ ```
313
+
314
+ ## Paginating
315
+
316
+ [See specification](http://jsonapi.org/format/#fetching-pagination)
317
+
318
+ ### Requesting
319
+
320
+ ```ruby
321
+ # makes request to /articles?page=2&per_page=30
322
+ articles = Article.page(2).per(30).to_a
323
+
324
+ # also makes request to /articles?page=2&per_page=30
325
+ articles = Article.paginate(page: 2, per_page: 30).to_a
326
+
327
+ # keep in mind that page number can be nil - in that case default number will be applied
328
+ # also makes request to /articles?page=1&per_page=30
329
+ articles = Article.paginate(page: nil, per_page: 30).to_a
330
+ ```
331
+
332
+ *Note: The mapping of pagination parameters is done by the `query_builder` which is [customizable](#custom-paginator).*
333
+
334
+ ### Browsing
335
+
336
+ If the response contains additional pagination links, you can also get at those:
337
+
338
+ ```ruby
339
+ articles = Article.paginate(page: 2, per_page: 30).to_a
340
+ articles.pages.next
341
+ articles.pages.last
342
+ ```
343
+
344
+ ### Library compatibility
345
+
346
+ A `JsonApiClient::ResultSet` object should be paginatable with both `kaminari` and `will_paginate`.
347
+
348
+ ## Filtering
349
+
350
+ [See specifiation](http://jsonapi.org/format/#fetching-filtering)
351
+
352
+ ```ruby
353
+ # makes request to /people?filter[name]=Jeff
354
+ Person.where(name: 'Jeff').all
355
+ ```
356
+
357
+ ## Schema
358
+
359
+ You can define schema within your client model. You can define basic types and set default values if you wish. If you declare a basic type, we will try to cast any input to be that type.
360
+
361
+ The added benefit of declaring your schema is that you can access fields before data is set (otherwise, you'll get a `NoMethodError`).
362
+
363
+ **Note: This is completely optional. This will set default values and handle typecasting.**
364
+
365
+ ### Example
366
+
367
+ ```ruby
368
+ class User < JsonApiClient::Resource
369
+ property :name, type: :string
370
+ property :is_admin, type: :boolean, default: false
371
+ property :points_accrued, type: :int, default: 0
372
+ property :averge_points_per_day, type: :float
373
+ end
374
+
375
+ # default values
376
+ u = User.new
377
+
378
+ u.name
379
+ # => nil
380
+
381
+ u.is_admin
382
+ # => false
383
+
384
+ u.points_accrued
385
+ # => 0
386
+
387
+ # casting
388
+ u.average_points_per_day = "0.3"
389
+ u.average_points_per_day
390
+ # => 0.3
391
+ ```
392
+
393
+ ### Types
394
+
395
+ The basic types that we allow are:
396
+
397
+ * `:int` or `:integer`
398
+ * `:float`
399
+ * `:string`
400
+ * `:time` - *Note: Include the time zone in the string if it's different than local time.
401
+ * `:boolean` - *Note: we will cast the string version of "true" and "false" to their respective values*
402
+
403
+ Also, we consider `nil` to be an acceptable value and will not cast the value.
404
+
405
+ Note : Do not map the primary key as int.
406
+
407
+ ## Customizing
408
+
409
+ ### Paths
410
+
411
+ You can customize this path by changing your resource's `table_name`:
412
+
413
+ ```ruby
414
+ module MyApi
415
+ class SomeResource < Base
416
+ def self.table_name
417
+ "foobar"
418
+ end
419
+ end
420
+ end
421
+
422
+ # requests http://example.com/foobar
423
+ MyApi::SomeResource.all
424
+ ```
425
+
426
+ ### Custom headers
427
+
428
+ You can inject custom headers on resource request by wrapping your code into block:
429
+ ```ruby
430
+ MyApi::SomeResource.with_headers(x_access_token: 'secure_token_here') do
431
+ MyApi::SomeResource.find(1)
432
+ end
433
+ ```
434
+
435
+ ### Connections
436
+
437
+ You can configure your API client to use a custom connection that implementes the `run` instance method. It should return data that your parser can handle. The default connection class wraps Faraday and lets you add middleware.
438
+
439
+ ```ruby
440
+ class NullConnection
441
+ def initialize(*args)
442
+ end
443
+
444
+ def run(request_method, path, params: nil, headers: {}, body: nil)
445
+ end
446
+
447
+ def use(*args); end
448
+ end
449
+
450
+ class CustomConnectionResource < TestResource
451
+ self.connection_class = NullConnection
452
+ end
453
+ ```
454
+
455
+ #### Connection Options
456
+
457
+ You can configure your connection using Faraday middleware. In general, you'll want
458
+ to do this in a base model that all your resources inherit from:
459
+
460
+ ```ruby
461
+ MyApi::Base.connection do |connection|
462
+ # set OAuth2 headers
463
+ connection.use FaradayMiddleware::OAuth2, 'MYTOKEN'
464
+
465
+ # log responses
466
+ connection.use Faraday::Response::Logger
467
+
468
+ connection.use MyCustomMiddleware
469
+ end
470
+
471
+ module MyApi
472
+ class User < Base
473
+ # will use the customized connection
474
+ end
475
+ end
476
+ ```
477
+
478
+ ##### Server errors handling
479
+
480
+ Non-success API response will cause the specific `JsonApiClient::Errors::SomeException` raised, depends on responded HTTP status.
481
+ Please refer to [JsonApiClient::Middleware::Status#handle_status](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/middleware/status.rb)
482
+ method for concrete status-to-exception mapping used out of the box.
483
+
484
+ JsonApiClient will try determine is failed API response JsonApi-compatible, if so - JsonApi error messages will be parsed from response body, and tracked as a part of particular exception message. In additional, `JsonApiClient::Errors::ServerError` exception will keep the actual HTTP status and message within its message.
485
+
486
+ ##### Custom status handler
487
+
488
+ You can change handling of response status using `connection_options`. For example you can override 400 status handling.
489
+ By default it raises `JsonApiClient::Errors::ClientError` but you can skip exception if you want to process errors from the server.
490
+ You need to provide a `proc` which should call `throw(:handled)` default handler for this status should be skipped.
491
+ ```ruby
492
+ class ApiBadRequestHandler
493
+ def self.call(_env)
494
+ # do not raise exception
495
+ end
496
+ end
497
+
498
+ class CustomUnauthorizedError < StandardError
499
+ attr_reader :env
500
+
501
+ def initialize(env)
502
+ @env = env
503
+ super('not authorized')
504
+ end
505
+ end
506
+
507
+ MyApi::Base.connection_options[:status_handlers] = {
508
+ 400 => ApiBadRequestHandler,
509
+ 401 => ->(env) { raise CustomUnauthorizedError, env }
510
+ }
511
+
512
+ module MyApi
513
+ class User < Base
514
+ # will use the customized status_handlers
515
+ end
516
+ end
517
+
518
+ user = MyApi::User.create(name: 'foo')
519
+ # server responds with { errors: [ { detail: 'bad request' } ] }
520
+ user.errors.messages # { base: ['bad request'] }
521
+ # on 401 it will raise CustomUnauthorizedError instead of JsonApiClient::Errors::NotAuthorized
522
+ ```
523
+
524
+ ##### Specifying an HTTP Proxy
525
+
526
+ All resources have a class method ```connection_options``` used to pass options to the JsonApiClient::Connection initializer.
527
+
528
+ ```ruby
529
+ MyApi::Base.connection_options[:proxy] = 'http://proxy.example.com'
530
+ MyApi::Base.connection do |connection|
531
+ # ...
532
+ end
533
+
534
+ module MyApi
535
+ class User < Base
536
+ # will use the customized connection with proxy
537
+ end
538
+ end
539
+ ```
540
+
541
+ ### Custom Parser
542
+
543
+ You can configure your API client to use a custom parser that implements the `parse` class method. It should return a `JsonApiClient::ResultSet` instance. You can use it by setting the parser attribute on your model:
544
+
545
+ ```ruby
546
+ class MyCustomParser
547
+ def self.parse(klass, response)
548
+ # …
549
+ # returns some ResultSet object
550
+ end
551
+ end
552
+
553
+ class MyApi::Base < JsonApiClient::Resource
554
+ self.parser = MyCustomParser
555
+ end
556
+ ```
557
+
558
+ ### Custom Query Builder
559
+
560
+ You can customize how the scope builder methods map to request parameters.
561
+
562
+ ```ruby
563
+ class MyQueryBuilder
564
+ def initialize(klass); end
565
+
566
+ def where(conditions = {})
567
+ end
568
+
569
+ # … add order, includes, paginate, page, first, build
570
+ end
571
+
572
+ class MyApi::Base < JsonApiClient::Resource
573
+ self.query_builder = MyQueryBuilder
574
+ end
575
+ ```
576
+
577
+ ### Custom Paginator
578
+
579
+ You can customize how your resources find pagination information from the response.
580
+
581
+ If the [existing paginator](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) fits your requirements but you don't use the default `page` and `per_page` params for pagination, you can customise the param keys as follows:
582
+
583
+ ```ruby
584
+ JsonApiClient::Paginating::Paginator.page_param = "number"
585
+ JsonApiClient::Paginating::Paginator.per_page_param = "size"
586
+ ```
587
+
588
+ Please note that this is a global configuration, so library authors should create a custom paginator that inherits `JsonApiClient::Paginating::Paginator` and configure the custom paginator to avoid modifying global config.
589
+
590
+ If the [existing paginator](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) does not fit your needs, you can create a custom paginator:
591
+
592
+ ```ruby
593
+ class MyPaginator
594
+ def initialize(result_set, data); end
595
+ # implement current_page, total_entries, etc
596
+ end
597
+
598
+ class MyApi::Base < JsonApiClient::Resource
599
+ self.paginator = MyPaginator
600
+ end
601
+ ```
602
+
603
+ ### NestedParamPaginator
604
+
605
+ The default `JsonApiClient::Paginating::Paginator` is not strict about how it handles the param keys ([#347](https://github.com/JsonApiClient/json_api_client/issues/347)). There is a second paginator that more rigorously adheres to the JSON:API pagination recommendation style of `page[page]=1&page[per_page]=10`.
606
+
607
+ If this second style suits your needs better, it is available as a class override:
608
+
609
+ ```ruby
610
+ class Order < JsonApiClient::Resource
611
+ self.paginator = JsonApiClient::Paginating::NestedParamPaginator
612
+ end
613
+ ```
614
+
615
+ You can also extend `NestedParamPaginator` in your custom paginators or assign the `page_param` or `per_page_param` as with the default version above.
616
+
617
+ ### Custom type
618
+
619
+ If your model must be named differently from classified type of resource you can easily customize it.
620
+ It will work both for defined and not defined relationships
621
+
622
+ ```ruby
623
+ class MyApi::Base < JsonApiClient::Resource
624
+ resolve_custom_type 'document--files', 'File'
625
+ end
626
+
627
+ class MyApi::File < MyApi::Base
628
+ def self.resource_name
629
+ 'document--files'
630
+ end
631
+ end
632
+ ```
633
+
634
+ ### Type Casting
635
+
636
+ You can define your own types and its casting mechanism for schema.
637
+
638
+ ```ruby
639
+ require 'money'
640
+ class MyMoneyCaster
641
+ def self.cast(value, default)
642
+ begin
643
+ Money.new(value, "USD")
644
+ rescue ArgumentError
645
+ default
646
+ end
647
+ end
648
+ end
649
+
650
+ JsonApiClient::Schema.register money: MyMoneyCaster
651
+
652
+ ```
653
+ and finally
654
+
655
+ ```ruby
656
+ class Order < JsonApiClient::Resource
657
+ property :total_amount, type: :money
658
+ end
659
+
660
+ ```
661
+
662
+ ### Safe singular resource fetching
663
+
664
+ That is a bit curios, but `json_api_client` returns an array from `.find` method, always.
665
+ The history of this fact was discussed [here](https://github.com/carwow/json_api_client/issues/75)
666
+
667
+ So, when we searching for a single resource by primary key, we typically write the things like
668
+
669
+ ```ruby
670
+ admin = User.find(id).first
671
+ ```
672
+
673
+ The next thing which we need to notice - `json_api_client` will just interpolate the incoming `.find` param to the end of API URL, just like that:
674
+
675
+ > http://somehost/api/v1/users/{id}
676
+
677
+ What will happen if we pass the blank id (nil or empty string) to the `.find` method then?.. Yeah, `json_api_client` will try to call the INDEX API endpoint instead of SHOW one:
678
+
679
+ > http://somehost/api/v1/users/
680
+
681
+ Lets sum all together - in case if `id` comes blank (from CGI for instance), we can silently receive the `admin` variable equal to some existing resource, with all the consequences.
682
+
683
+ Even worse, `admin` variable can equal to *random* resource, depends on ordering applied by INDEX endpoint.
684
+
685
+ If you prefer to get `JsonApiClient::Errors::NotFound` raised, please define in your base Resource class:
686
+
687
+ ```ruby
688
+ class Resource < JsonApiClient::Resource
689
+ self.raise_on_blank_find_param = true
690
+ end
691
+ ```
692
+
693
+ ## Contributing
694
+
695
+ Contributions are welcome! Please fork this repo and send a pull request. Your pull request should have:
696
+
697
+ * a description about what's broken or what the desired functionality is
698
+ * a test illustrating the bug or new feature
699
+ * the code to fix the bug
700
+
701
+ Ideally, the PR has 2 commits - the first showing the failed test and the second with the fix - although this is not
702
+ required. The commits will be squashed into master once accepted.
703
+
704
+ ## Changelog
705
+
706
+ See [changelog](https://github.com/carwow/json_api_client/blob/master/CHANGELOG.md)