Skip to content

Commit 0f6be6e

Browse files
authored
Merge branch 'master' into update-changelog-after-1-10
2 parents ec35cde + 94f45e2 commit 0f6be6e

18 files changed

+816
-46
lines changed

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,10 @@ Gemfile.lock
2424
*.gemfile.lock
2525

2626
/coverage
27+
28+
# IntellJ/RubyMine
29+
*.iml
30+
.idea/
31+
32+
# asdf config
33+
.tool-versions

CHANGELOG.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@
22

33
## Unreleased
44

5+
## 1.13.0
6+
7+
- [#348](https://github.com/JsonApiClient/json_api_client/pull/348) - add NestedParamPaginator to address inconsistency in handling of pagination query string params (issue [#347](https://github.com/JsonApiClient/json_api_client/issues/347)).
8+
9+
## 1.12.2
10+
11+
- [#350](https://github.com/JsonApiClient/json_api_client/pull/350) - fix resource including with blank `relationships` response data
12+
13+
## 1.12.1
14+
15+
- [#349](https://github.com/JsonApiClient/json_api_client/pull/349) - fix resource including for STI objects
16+
17+
## 1.12.0
18+
19+
- [#345](https://github.com/JsonApiClient/json_api_client/pull/345) - track the real HTTP reason of ApiErrors
20+
21+
## 1.11.0
22+
23+
- [#344](https://github.com/JsonApiClient/json_api_client/pull/344) - introduce safe singular resource fetching with `raise_on_blank_find_param` resource setting
24+
525
## 1.10.0
626

727
- [#335](https://github.com/JsonApiClient/json_api_client/pull/335) - access to assigned relationship
@@ -31,7 +51,7 @@
3151
* fix relationships passing to `new` and `create` methods
3252
* fix false positive tests on create
3353
* refactor tests on create/update to prevent false same positive tests in future
34-
54+
3555
- [#315](https://github.com/JsonApiClient/json_api_client/pull/315) - add shallow_path feature to belongs_to
3656
* add `shallow_path` options to belongs_to to use model w/ and w/o nesting in parent resource
3757

README.md

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
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).
44

5-
*Note: master is currently tracking the 1.0.0 specification. If you're looking for the older code, see [0.x branch](https://github.com/chingor13/json_api_client/tree/0.x)*
5+
*Note: master is currently tracking the 1.0.0 specification. If you're looking for the older code, see [0.x branch](https://github.com/JsonApiClient/json_api_client/tree/0.x)*
66

77
## Usage
88

@@ -52,14 +52,14 @@ u.update_attributes(
5252
c: "d"
5353
)
5454

55-
u.persisted?
55+
u.persisted?
5656
# => true
5757

5858
u.destroy
5959

60-
u.destroyed?
60+
u.destroyed?
6161
# => true
62-
u.persisted?
62+
u.persisted?
6363
# => false
6464

6565
u = MyApi::Person.create(
@@ -164,7 +164,7 @@ module MyApi
164164
class Account < JsonApiClient::Resource
165165
belongs_to :user
166166
end
167-
167+
168168
class Customer < JsonApiClient::Resource
169169
belongs_to :user, shallow_path: true
170170
end
@@ -474,9 +474,17 @@ module MyApi
474474
end
475475
```
476476
477+
##### Server errors handling
478+
479+
Non-success API response will cause the specific `JsonApiClient::Errors::SomeException` raised, depends on responded HTTP status.
480+
Please refer to [JsonApiClient::Middleware::Status#handle_status](https://github.com/JsonApiClient/json_api_client/blob/master/lib/json_api_client/middleware/status.rb)
481+
method for concrete status-to-exception mapping used out of the box.
482+
483+
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.
484+
477485
##### Custom status handler
478486
479-
You can change handling of response status using `connection_options`. For example you can override 400 status handling.
487+
You can change handling of response status using `connection_options`. For example you can override 400 status handling.
480488
By default it raises `JsonApiClient::Errors::ClientError` but you can skip exception if you want to process errors from the server.
481489
You need to provide a `proc` which should call `throw(:handled)` default handler for this status should be skipped.
482490
```ruby
@@ -569,7 +577,7 @@ end
569577
570578
You can customize how your resources find pagination information from the response.
571579
572-
If the [existing paginator](https://github.com/chingor13/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:
580+
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:
573581
574582
```ruby
575583
JsonApiClient::Paginating::Paginator.page_param = "number"
@@ -578,7 +586,7 @@ JsonApiClient::Paginating::Paginator.per_page_param = "size"
578586
579587
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.
580588
581-
If the [existing paginator](https://github.com/chingor13/json_api_client/blob/master/lib/json_api_client/paginating/paginator.rb) does not fit your needs, you can create a custom paginator:
589+
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:
582590
583591
```ruby
584592
class MyPaginator
@@ -591,6 +599,20 @@ class MyApi::Base < JsonApiClient::Resource
591599
end
592600
```
593601
602+
### NestedParamPaginator
603+
604+
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`.
605+
606+
If this second style suits your needs better, it is available as a class override:
607+
608+
```ruby
609+
class Order < JsonApiClient::Resource
610+
self.paginator = JsonApiClient::Paginating::NestedParamPaginator
611+
end
612+
```
613+
614+
You can also extend `NestedParamPaginator` in your custom paginators or assign the `page_param` or `per_page_param` as with the default version above.
615+
594616
### Custom type
595617
596618
If your model must be named differently from classified type of resource you can easily customize it.
@@ -636,6 +658,37 @@ end
636658

637659
```
638660
661+
### Safe singular resource fetching
662+
663+
That is a bit curios, but `json_api_client` returns an array from `.find` method, always.
664+
The history of this fact was discussed [here](https://github.com/JsonApiClient/json_api_client/issues/75)
665+
666+
So, when we searching for a single resource by primary key, we typically write the things like
667+
668+
```ruby
669+
admin = User.find(id).first
670+
```
671+
672+
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:
673+
674+
> http://somehost/api/v1/users/{id}
675+
676+
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:
677+
678+
> http://somehost/api/v1/users/
679+
680+
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.
681+
682+
Even worse, `admin` variable can equal to *random* resource, depends on ordering applied by INDEX endpoint.
683+
684+
If you prefer to get `JsonApiClient::Errors::NotFound` raised, please define in your base Resource class:
685+
686+
```ruby
687+
class Resource < JsonApiClient::Resource
688+
self.raise_on_blank_find_param = true
689+
end
690+
```
691+
639692
## Contributing
640693
641694
Contributions are welcome! Please fork this repo and send a pull request. Your pull request should have:
@@ -649,4 +702,4 @@ required. The commits will be squashed into master once accepted.
649702
650703
## Changelog
651704
652-
See [changelog](https://github.com/chingor13/json_api_client/blob/master/CHANGELOG.md)
705+
See [changelog](https://github.com/JsonApiClient/json_api_client/blob/master/CHANGELOG.md)

json_api_client.gemspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ Gem::Specification.new do |s|
1616
s.add_dependency "faraday_middleware", '~> 0.9'
1717
s.add_dependency "addressable", '~> 2.2'
1818
s.add_dependency "activemodel", '>= 3.2.0'
19+
s.add_dependency "rack", '>= 0.2'
1920

20-
s.add_development_dependency "webmock"
21+
s.add_development_dependency "webmock", '~> 3.5.1'
2122
s.add_development_dependency "mocha"
2223

2324
s.license = "MIT"

lib/json_api_client/errors.rb

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
1+
require 'rack'
2+
13
module JsonApiClient
24
module Errors
35
class ApiError < StandardError
46
attr_reader :env
7+
58
def initialize(env, msg = nil)
6-
super msg
79
@env = env
10+
# Try to fetch json_api errors from response
11+
msg = track_json_api_errors(msg)
12+
13+
super msg
14+
end
15+
16+
private
17+
18+
# Try to fetch json_api errors from response
19+
def track_json_api_errors(msg)
20+
return msg unless env.try(:body).kind_of?(Hash) || env.body.key?('errors')
21+
22+
errors_msg = env.body['errors'].map { |e| e['title'] }.compact.join('; ').presence
23+
return msg unless errors_msg
24+
25+
msg.nil? ? errors_msg : "#{msg} (#{errors_msg})"
26+
# Just to be sure that it is back compatible
27+
rescue StandardError
28+
msg
829
end
930
end
1031

@@ -21,7 +42,13 @@ class ConnectionError < ApiError
2142
end
2243

2344
class ServerError < ApiError
24-
def initialize(env, msg = 'Internal server error')
45+
def initialize(env, msg = nil)
46+
msg ||= begin
47+
status = env.status
48+
message = ::Rack::Utils::HTTP_STATUS_CODES[status]
49+
"#{status} #{message}"
50+
end
51+
2552
super env, msg
2653
end
2754
end
@@ -36,20 +63,23 @@ class NotFound < ServerError
3663
attr_reader :uri
3764
def initialize(uri)
3865
@uri = uri
39-
end
40-
def message
41-
"Couldn't find resource at: #{uri.to_s}"
66+
67+
msg = "Couldn't find resource at: #{uri.to_s}"
68+
super nil, msg
4269
end
4370
end
4471

72+
class InternalServerError < ServerError
73+
end
74+
4575
class UnexpectedStatus < ServerError
4676
attr_reader :code, :uri
4777
def initialize(code, uri)
4878
@code = code
4979
@uri = uri
50-
end
51-
def message
52-
"Unexpected response status: #{code} from: #{uri.to_s}"
80+
81+
msg = "Unexpected response status: #{code} from: #{uri.to_s}"
82+
super nil, msg
5383
end
5484
end
5585
end

lib/json_api_client/included_data.rb

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,30 @@ class IncludedData
44

55
def initialize(result_set, data)
66
record_class = result_set.record_class
7-
included_set = data.map do |datum|
8-
type = datum["type"]
7+
grouped_data = data.group_by{|datum| datum["type"]}
8+
grouped_included_set = grouped_data.each_with_object({}) do |(type, records), h|
99
klass = Utils.compute_type(record_class, record_class.key_formatter.unformat(type).singularize.classify)
10-
params = klass.parser.parameters_from_resource(datum)
11-
resource = klass.load(params)
12-
resource.last_result_set = result_set
13-
resource
10+
h[type] = records.map do |record|
11+
params = klass.parser.parameters_from_resource(record)
12+
klass.load(params).tap do |resource|
13+
resource.last_result_set = result_set
14+
end
15+
end
1416
end
1517

16-
included_set.concat(result_set) if record_class.search_included_in_result_set
17-
@data = included_set.group_by(&:type).inject({}) do |h, (type, resources)|
18-
h[type] = resources.index_by(&:id)
19-
h
18+
if record_class.search_included_in_result_set
19+
# deep_merge overrides the nested Arrays o_O
20+
# {a: [1,2]}.deep_merge(a: [3,4]) # => {a: [3,4]}
21+
grouped_included_set.merge!(result_set.group_by(&:type)) do |_, resources1, resources2|
22+
resources1 + resources2
23+
end
2024
end
25+
26+
grouped_included_set.each do |type, resources|
27+
grouped_included_set[type] = resources.index_by(&:id)
28+
end
29+
30+
@data = grouped_included_set
2131
end
2232

2333
def data_for(method_name, definition)

lib/json_api_client/middleware/status.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ def handle_status(code, env)
4444
# Allow to proceed as resource errors will be populated
4545
when 400..499
4646
raise Errors::ClientError, env
47-
when 500..599
47+
when 500
48+
raise Errors::InternalServerError, env
49+
when 501..599
4850
raise Errors::ServerError, env
4951
else
5052
raise Errors::UnexpectedStatus.new(code, env[:url])

lib/json_api_client/paginating.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module JsonApiClient
22
module Paginating
33
autoload :Paginator, 'json_api_client/paginating/paginator'
4+
autoload :NestedParamPaginator, 'json_api_client/paginating/nested_param_paginator'
45
end
5-
end
6+
end

0 commit comments

Comments
 (0)