jsonapi.rb 1.6.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +25 -1
- data/lib/jsonapi/active_model_error_serializer.rb +0 -3
- data/lib/jsonapi/error_serializer.rb +6 -1
- data/lib/jsonapi/filtering.rb +6 -2
- data/lib/jsonapi/pagination.rb +25 -5
- data/lib/jsonapi/rails.rb +18 -10
- data/lib/jsonapi/version.rb +1 -1
- data/spec/deserialization_spec.rb +87 -0
- data/spec/dummy.rb +162 -0
- data/spec/errors_spec.rb +168 -0
- data/spec/fetching_spec.rb +65 -0
- data/spec/filtering_spec.rb +101 -0
- data/spec/pagination_spec.rb +246 -0
- data/spec/spec_helper.rb +87 -0
- metadata +36 -25
- data/.github/ISSUE_TEMPLATE.md +0 -16
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -17
- data/.github/workflows/ci.yml +0 -36
- data/.gitignore +0 -3
- data/.rspec +0 -3
- data/.rubocop.yml +0 -36
- data/.yardstick.yml +0 -29
- data/Gemfile +0 -4
- data/Rakefile +0 -34
- data/jsonapi.rb.gemspec +0 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d531366279855044f83d35f4526a72156c711895290ab4dbce73f1c7615f1079
|
4
|
+
data.tar.gz: 63bd2be61a78d79a64bc739cbb492ee75a9e2f90a95389d55f133a2565627abb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e602d6c993cd0259ac0b81c57eca16aee525f53ed0e5b4ac8014a0cb500528d579541375ce7735f0f191f3f732af9178ebbfdf7d4615eb967cfb244d1796cd36
|
7
|
+
data.tar.gz: 46a957ded4247be1607f6a4d84246da42a44fe41c58c55e2a34a914e958a28731531bc5e8a3f0d0141f571e17dce19c9c1f6f58845fc3e7db874d763dfcaffbb
|
data/README.md
CHANGED
@@ -39,7 +39,7 @@ The available features include:
|
|
39
39
|
[sparse fields](https://jsonapi.org/format/#fetching-sparse-fieldsets))
|
40
40
|
* [filtering](https://jsonapi.org/format/#fetching-filtering) and
|
41
41
|
[sorting](https://jsonapi.org/format/#fetching-sorting) of the data
|
42
|
-
(powered by Ransack)
|
42
|
+
(powered by Ransack, soft-dependency)
|
43
43
|
* [pagination](https://jsonapi.org/format/#fetching-pagination) support
|
44
44
|
|
45
45
|
## But how?
|
@@ -49,6 +49,16 @@ and [Ransack](https://github.com/activerecord-hackery/ransack).
|
|
49
49
|
|
50
50
|
Thanks to everyone who worked on these amazing projects!
|
51
51
|
|
52
|
+
## Sponsors
|
53
|
+
|
54
|
+
I'm grateful for the following companies for supporting this project!
|
55
|
+
|
56
|
+
<p align="center">
|
57
|
+
<a href="https://www.luneteyewear.com"><img src="https://user-images.githubusercontent.com/112147/136836142-2bfba96e-447f-4eb6-b137-2445aee81b37.png"/></a>
|
58
|
+
<a href="https://www.startuplandia.io"><img src="https://user-images.githubusercontent.com/112147/136836147-93f8ab17-2465-4477-a7ab-e38255483c66.png"/></a>
|
59
|
+
</p>
|
60
|
+
|
61
|
+
|
52
62
|
## Installation
|
53
63
|
|
54
64
|
Add this line to your application's Gemfile:
|
@@ -231,6 +241,8 @@ to filter and sort over a collection of records.
|
|
231
241
|
The support is pretty extended and covers also relationships and composite
|
232
242
|
matchers.
|
233
243
|
|
244
|
+
Please add `ransack` to your `Gemfile` in order to benefit from this functionality!
|
245
|
+
|
234
246
|
Here's an example:
|
235
247
|
|
236
248
|
```ruby
|
@@ -290,6 +302,7 @@ class MyController < ActionController::Base
|
|
290
302
|
render jsonapi: paginated
|
291
303
|
end
|
292
304
|
end
|
305
|
+
|
293
306
|
end
|
294
307
|
```
|
295
308
|
|
@@ -306,6 +319,17 @@ use the `jsonapi_pagination_meta` method:
|
|
306
319
|
end
|
307
320
|
|
308
321
|
```
|
322
|
+
|
323
|
+
If you want to change the default number of items per page or define a custom logic to handle page size, use the
|
324
|
+
`jsonapi_page_size` method:
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
def jsonapi_page_size(pagination_params)
|
328
|
+
per_page = pagination_params[:size].to_f.to_i
|
329
|
+
per_page = 30 if per_page > 30 || per_page < 1
|
330
|
+
per_page
|
331
|
+
end
|
332
|
+
```
|
309
333
|
### Deserialization
|
310
334
|
|
311
335
|
`JSONAPI::Deserialization` provides a helper to transform a `JSONAPI` document
|
@@ -5,7 +5,6 @@ module JSONAPI
|
|
5
5
|
class ErrorSerializer
|
6
6
|
include JSONAPI::Serializer
|
7
7
|
|
8
|
-
set_id :object_id
|
9
8
|
set_type :error
|
10
9
|
|
11
10
|
# Object/Hash attribute helpers.
|
@@ -15,6 +14,12 @@ module JSONAPI
|
|
15
14
|
end
|
16
15
|
end
|
17
16
|
|
17
|
+
# Overwrite the ID extraction method, to skip validations
|
18
|
+
#
|
19
|
+
# @return [NilClass]
|
20
|
+
def self.id_from_record(_record, _params)
|
21
|
+
end
|
22
|
+
|
18
23
|
# Remap the root key to `errors`
|
19
24
|
#
|
20
25
|
# @return [Hash]
|
data/lib/jsonapi/filtering.rb
CHANGED
data/lib/jsonapi/pagination.rb
CHANGED
@@ -43,6 +43,8 @@ module JSONAPI
|
|
43
43
|
original_url = request.base_url + request.path + '?'
|
44
44
|
|
45
45
|
pagination.each do |page_name, number|
|
46
|
+
next if page_name == :records
|
47
|
+
|
46
48
|
original_params[:page][:number] = number
|
47
49
|
links[page_name] = original_url + CGI.unescape(
|
48
50
|
original_params.to_query
|
@@ -63,7 +65,7 @@ module JSONAPI
|
|
63
65
|
numbers = { current: page }
|
64
66
|
|
65
67
|
if resources.respond_to?(:unscope)
|
66
|
-
total = resources.unscope(:limit, :offset, :order).
|
68
|
+
total = resources.unscope(:limit, :offset, :order).size
|
67
69
|
else
|
68
70
|
# Try to fetch the cached size first
|
69
71
|
total = resources.instance_variable_get(:@original_size)
|
@@ -82,6 +84,10 @@ module JSONAPI
|
|
82
84
|
numbers[:last] = last_page
|
83
85
|
end
|
84
86
|
|
87
|
+
if total.present?
|
88
|
+
numbers[:records] = total
|
89
|
+
end
|
90
|
+
|
85
91
|
numbers
|
86
92
|
end
|
87
93
|
|
@@ -89,16 +95,30 @@ module JSONAPI
|
|
89
95
|
#
|
90
96
|
# @return [Array] with the offset, limit and the current page number
|
91
97
|
def jsonapi_pagination_params
|
92
|
-
def_per_page = self.class.const_get(:JSONAPI_PAGE_SIZE).to_i
|
93
|
-
|
94
98
|
pagination = params[:page].try(:slice, :number, :size) || {}
|
95
|
-
per_page = pagination
|
96
|
-
per_page = def_per_page if per_page > def_per_page || per_page < 1
|
99
|
+
per_page = jsonapi_page_size(pagination)
|
97
100
|
num = [1, pagination[:number].to_f.to_i].max
|
98
101
|
|
99
102
|
[(num - 1) * per_page, per_page, num]
|
100
103
|
end
|
101
104
|
|
105
|
+
# Retrieves the default page size
|
106
|
+
#
|
107
|
+
# @param per_page_param [Hash] opts the paginations params
|
108
|
+
# @option opts [String] :number the page number requested
|
109
|
+
# @option opts [String] :size the page size requested
|
110
|
+
#
|
111
|
+
# @return [Integer]
|
112
|
+
def jsonapi_page_size(pagination_params)
|
113
|
+
per_page = pagination_params[:size].to_f.to_i
|
114
|
+
|
115
|
+
return self.class
|
116
|
+
.const_get(:JSONAPI_PAGE_SIZE)
|
117
|
+
.to_i if per_page < 1
|
118
|
+
|
119
|
+
per_page
|
120
|
+
end
|
121
|
+
|
102
122
|
# Fallback to Rack's parsed query string when Rails is not available
|
103
123
|
#
|
104
124
|
# @return [Hash]
|
data/lib/jsonapi/rails.rb
CHANGED
@@ -37,7 +37,7 @@ module JSONAPI
|
|
37
37
|
# @return [NilClass]
|
38
38
|
def self.add_errors_renderer!
|
39
39
|
ActionController::Renderers.add(:jsonapi_errors) do |resource, options|
|
40
|
-
self.content_type
|
40
|
+
self.content_type = Mime[:jsonapi] if self.media_type.nil?
|
41
41
|
|
42
42
|
many = JSONAPI::Rails.is_collection?(resource, options[:is_collection])
|
43
43
|
resource = [resource] unless many
|
@@ -47,7 +47,7 @@ module JSONAPI
|
|
47
47
|
) unless resource.is_a?(ActiveModel::Errors)
|
48
48
|
|
49
49
|
errors = []
|
50
|
-
model = resource.instance_variable_get(
|
50
|
+
model = resource.instance_variable_get(:@base)
|
51
51
|
|
52
52
|
if respond_to?(:jsonapi_serializer_class, true)
|
53
53
|
model_serializer = jsonapi_serializer_class(model, false)
|
@@ -55,8 +55,18 @@ module JSONAPI
|
|
55
55
|
model_serializer = JSONAPI::Rails.serializer_class(model, false)
|
56
56
|
end
|
57
57
|
|
58
|
-
details =
|
59
|
-
|
58
|
+
details = {}
|
59
|
+
if ::Rails.gem_version >= Gem::Version.new('6.1')
|
60
|
+
resource.each do |error|
|
61
|
+
attr = error.attribute
|
62
|
+
details[attr] ||= []
|
63
|
+
details[attr] << error.detail.merge(message: error.message)
|
64
|
+
end
|
65
|
+
elsif resource.respond_to?(:details)
|
66
|
+
details = resource.details
|
67
|
+
else
|
68
|
+
details = resource.messages
|
69
|
+
end
|
60
70
|
|
61
71
|
details.each do |error_key, error_hashes|
|
62
72
|
error_hashes.each do |error_hash|
|
@@ -80,7 +90,7 @@ module JSONAPI
|
|
80
90
|
# @return [NilClass]
|
81
91
|
def self.add_renderer!
|
82
92
|
ActionController::Renderers.add(:jsonapi) do |resource, options|
|
83
|
-
self.content_type
|
93
|
+
self.content_type = Mime[:jsonapi] if self.media_type.nil?
|
84
94
|
|
85
95
|
JSONAPI_METHODS_MAPPING.to_a[0..1].each do |opt, method_name|
|
86
96
|
next unless respond_to?(method_name, true)
|
@@ -90,7 +100,7 @@ module JSONAPI
|
|
90
100
|
# If it's an empty collection, return it directly.
|
91
101
|
many = JSONAPI::Rails.is_collection?(resource, options[:is_collection])
|
92
102
|
if many && !resource.any?
|
93
|
-
return options.slice(:meta, :links).merge(data: []).to_json
|
103
|
+
return options.slice(:meta, :links).compact.merge(data: []).to_json
|
94
104
|
end
|
95
105
|
|
96
106
|
JSONAPI_METHODS_MAPPING.to_a[2..-1].each do |opt, method_name|
|
@@ -111,15 +121,13 @@ module JSONAPI
|
|
111
121
|
|
112
122
|
# Checks if an object is a collection
|
113
123
|
#
|
114
|
-
#
|
124
|
+
# Basically forwards it to a [JSONAPI::Serializer] as there's no public API
|
115
125
|
#
|
116
126
|
# @param resource [Object] to check
|
117
127
|
# @param force_is_collection [NilClass] flag to overwrite
|
118
128
|
# @return [TrueClass] upon success
|
119
129
|
def self.is_collection?(resource, force_is_collection = nil)
|
120
|
-
|
121
|
-
|
122
|
-
resource.respond_to?(:size) && !resource.respond_to?(:each_pair)
|
130
|
+
JSONAPI::ErrorSerializer.is_collection?(resource, force_is_collection)
|
123
131
|
end
|
124
132
|
|
125
133
|
# Resolves resource serializer class
|
data/lib/jsonapi/version.rb
CHANGED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe JSONAPI::Deserialization do
|
4
|
+
let(:jsonapi_deserialize) { UsersController.new.method(:jsonapi_deserialize) }
|
5
|
+
let(:document) do
|
6
|
+
{
|
7
|
+
data: {
|
8
|
+
id: 1,
|
9
|
+
type: 'note',
|
10
|
+
attributes: {
|
11
|
+
title: 'Title 1',
|
12
|
+
date: '2015-12-20'
|
13
|
+
},
|
14
|
+
relationships: {
|
15
|
+
author: {
|
16
|
+
data: {
|
17
|
+
type: 'user',
|
18
|
+
id: 2
|
19
|
+
}
|
20
|
+
},
|
21
|
+
second_author: {
|
22
|
+
data: nil
|
23
|
+
},
|
24
|
+
notes: {
|
25
|
+
data: [
|
26
|
+
{
|
27
|
+
type: 'note',
|
28
|
+
id: 3
|
29
|
+
},
|
30
|
+
{
|
31
|
+
type: 'note',
|
32
|
+
id: 4
|
33
|
+
}
|
34
|
+
]
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#jsonapi_deserialize' do
|
42
|
+
it do
|
43
|
+
expect(jsonapi_deserialize.call(document)).to eq(
|
44
|
+
'id' => 1,
|
45
|
+
'date' => '2015-12-20',
|
46
|
+
'title' => 'Title 1',
|
47
|
+
'author_id' => 2,
|
48
|
+
'second_author_id' => nil,
|
49
|
+
'note_ids' => [3, 4]
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'with `only`' do
|
54
|
+
it do
|
55
|
+
expect(jsonapi_deserialize.call(document, only: :notes)).to eq(
|
56
|
+
'note_ids' => [3, 4]
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'with `except`' do
|
62
|
+
it do
|
63
|
+
expect(
|
64
|
+
jsonapi_deserialize.call(document, except: [:date, :title])
|
65
|
+
).to eq(
|
66
|
+
'id' => 1,
|
67
|
+
'author_id' => 2,
|
68
|
+
'second_author_id' => nil,
|
69
|
+
'note_ids' => [3, 4]
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'with `polymorphic`' do
|
75
|
+
it do
|
76
|
+
expect(
|
77
|
+
jsonapi_deserialize.call(
|
78
|
+
document, only: :author, polymorphic: :author
|
79
|
+
)
|
80
|
+
).to eq(
|
81
|
+
'author_id' => 2,
|
82
|
+
'author_type' => User.name
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/spec/dummy.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'rails/all'
|
3
|
+
require 'ransack'
|
4
|
+
require 'jsonapi'
|
5
|
+
|
6
|
+
Rails.logger = Logger.new(STDOUT)
|
7
|
+
Rails.logger.level = ENV['LOG_LEVEL'] || Logger::WARN
|
8
|
+
|
9
|
+
JSONAPI::Rails.install!
|
10
|
+
|
11
|
+
ActiveRecord::Base.logger = Rails.logger
|
12
|
+
ActiveRecord::Base.establish_connection(
|
13
|
+
ENV['DATABASE_URL'] || 'sqlite3::memory:'
|
14
|
+
)
|
15
|
+
|
16
|
+
ActiveRecord::Schema.define do
|
17
|
+
create_table :users, force: true do |t|
|
18
|
+
t.string :first_name
|
19
|
+
t.string :last_name
|
20
|
+
t.timestamps
|
21
|
+
end
|
22
|
+
|
23
|
+
create_table :notes, force: true do |t|
|
24
|
+
t.string :title
|
25
|
+
t.integer :user_id
|
26
|
+
t.integer :quantity
|
27
|
+
t.timestamps
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class User < ActiveRecord::Base
|
32
|
+
has_many :notes
|
33
|
+
end
|
34
|
+
|
35
|
+
class Note < ActiveRecord::Base
|
36
|
+
validates_format_of :title, without: /BAD_TITLE/
|
37
|
+
validates_numericality_of :quantity, less_than: 100, if: :quantity?
|
38
|
+
belongs_to :user, required: true
|
39
|
+
end
|
40
|
+
|
41
|
+
class CustomNoteSerializer
|
42
|
+
include JSONAPI::Serializer
|
43
|
+
|
44
|
+
set_type :note
|
45
|
+
belongs_to :user
|
46
|
+
attributes(:title, :quantity, :created_at, :updated_at)
|
47
|
+
end
|
48
|
+
|
49
|
+
class UserSerializer
|
50
|
+
include JSONAPI::Serializer
|
51
|
+
|
52
|
+
has_many :notes, serializer: CustomNoteSerializer
|
53
|
+
attributes(:last_name, :created_at, :updated_at)
|
54
|
+
|
55
|
+
attribute :first_name do |object, params|
|
56
|
+
if params[:first_name_upcase]
|
57
|
+
object.first_name.upcase
|
58
|
+
else
|
59
|
+
object.first_name
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Dummy < Rails::Application
|
65
|
+
secrets.secret_key_base = '_'
|
66
|
+
config.hosts << 'www.example.com' if config.respond_to?(:hosts)
|
67
|
+
|
68
|
+
routes.draw do
|
69
|
+
scope defaults: { format: :jsonapi } do
|
70
|
+
resources :users, only: [:index]
|
71
|
+
resources :notes, only: [:update]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class UsersController < ActionController::Base
|
77
|
+
include JSONAPI::Fetching
|
78
|
+
include JSONAPI::Filtering
|
79
|
+
include JSONAPI::Pagination
|
80
|
+
include JSONAPI::Deserialization
|
81
|
+
|
82
|
+
def index
|
83
|
+
allowed_fields = [
|
84
|
+
:first_name, :last_name, :created_at,
|
85
|
+
:notes_created_at, :notes_quantity
|
86
|
+
]
|
87
|
+
options = { sort_with_expressions: true }
|
88
|
+
|
89
|
+
jsonapi_filter(User.all, allowed_fields, options) do |filtered|
|
90
|
+
result = filtered.result
|
91
|
+
|
92
|
+
if params[:sort].to_s.include?('notes_quantity')
|
93
|
+
render jsonapi: result.group('id').to_a
|
94
|
+
return
|
95
|
+
end
|
96
|
+
|
97
|
+
result = result.to_a if params[:as_list]
|
98
|
+
|
99
|
+
jsonapi_paginate(result) do |paginated|
|
100
|
+
render jsonapi: paginated
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
def jsonapi_meta(resources)
|
107
|
+
{
|
108
|
+
many: true,
|
109
|
+
pagination: jsonapi_pagination_meta(resources)
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
def jsonapi_serializer_params
|
114
|
+
{
|
115
|
+
first_name_upcase: params[:upcase]
|
116
|
+
}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class NotesController < ActionController::Base
|
121
|
+
include JSONAPI::Errors
|
122
|
+
include JSONAPI::Deserialization
|
123
|
+
|
124
|
+
def update
|
125
|
+
raise_error! if params[:id] == 'tada'
|
126
|
+
|
127
|
+
note = Note.find(params[:id])
|
128
|
+
|
129
|
+
if note.update(note_params)
|
130
|
+
render jsonapi: note
|
131
|
+
else
|
132
|
+
note.errors.add(:title, message: 'has typos') if note.errors.key?(:title)
|
133
|
+
|
134
|
+
render jsonapi_errors: note.errors, status: :unprocessable_entity
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
def render_jsonapi_internal_server_error(exception)
|
140
|
+
Rails.logger.error(exception)
|
141
|
+
super(exception)
|
142
|
+
end
|
143
|
+
|
144
|
+
def jsonapi_serializer_class(resource, is_collection)
|
145
|
+
JSONAPI::Rails.serializer_class(resource, is_collection)
|
146
|
+
rescue NameError
|
147
|
+
klass = resource.class
|
148
|
+
klass = resource.first.class if is_collection
|
149
|
+
"Custom#{klass.name}Serializer".constantize
|
150
|
+
end
|
151
|
+
|
152
|
+
def note_params
|
153
|
+
# Will trigger required attribute error handling
|
154
|
+
params.require(:data).require(:attributes).require(:title)
|
155
|
+
|
156
|
+
jsonapi_deserialize(params)
|
157
|
+
end
|
158
|
+
|
159
|
+
def jsonapi_meta(resources)
|
160
|
+
{ single: true }
|
161
|
+
end
|
162
|
+
end
|
data/spec/errors_spec.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe NotesController, type: :request do
|
4
|
+
describe 'PUT /notes/:id' do
|
5
|
+
let(:note) { create_note }
|
6
|
+
let(:note_id) { note.id }
|
7
|
+
let(:user) { note.user }
|
8
|
+
let(:user_id) { user.id }
|
9
|
+
let(:note_params) do
|
10
|
+
{
|
11
|
+
data: {
|
12
|
+
attributes: { title: FFaker::Company.name },
|
13
|
+
relationships: { user: { data: { id: user_id } } }
|
14
|
+
}
|
15
|
+
}
|
16
|
+
end
|
17
|
+
let(:params) { note_params }
|
18
|
+
|
19
|
+
before do
|
20
|
+
put(note_path(note_id), params: params.to_json, headers: jsonapi_headers)
|
21
|
+
end
|
22
|
+
|
23
|
+
it do
|
24
|
+
expect(response).to have_http_status(:ok)
|
25
|
+
expect(response_json['data']).to have_id(note.id.to_s)
|
26
|
+
expect(response_json['meta']).to eq('single' => true)
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with a missing parameter in the payload' do
|
30
|
+
let(:params) { {} }
|
31
|
+
|
32
|
+
it do
|
33
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
34
|
+
expect(response_json['errors'].size).to eq(1)
|
35
|
+
expect(response_json['errors'][0]['status']).to eq('422')
|
36
|
+
expect(response_json['errors'][0]['title'])
|
37
|
+
.to eq(Rack::Utils::HTTP_STATUS_CODES[422])
|
38
|
+
expect(response_json['errors'][0]['source']).to eq('pointer' => '')
|
39
|
+
expect(response_json['errors'][0]['detail']).to be_nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with an invalid payload' do
|
44
|
+
let(:params) do
|
45
|
+
payload = note_params.dup
|
46
|
+
payload[:data][:relationships][:user][:data][:id] = nil
|
47
|
+
payload
|
48
|
+
end
|
49
|
+
|
50
|
+
it do
|
51
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
52
|
+
expect(response_json['errors'].size).to eq(1)
|
53
|
+
expect(response_json['errors'][0]['status']).to eq('422')
|
54
|
+
expect(response_json['errors'][0]['code']).to include('blank')
|
55
|
+
expect(response_json['errors'][0]['title'])
|
56
|
+
.to eq(Rack::Utils::HTTP_STATUS_CODES[422])
|
57
|
+
expect(response_json['errors'][0]['source'])
|
58
|
+
.to eq('pointer' => '/data/relationships/user')
|
59
|
+
if Rails.gem_version >= Gem::Version.new('6.1')
|
60
|
+
expect(response_json['errors'][0]['detail'])
|
61
|
+
.to eq('User must exist')
|
62
|
+
else
|
63
|
+
expect(response_json['errors'][0]['detail'])
|
64
|
+
.to eq('User can\'t be blank')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'required by validations' do
|
69
|
+
let(:params) do
|
70
|
+
payload = note_params.dup
|
71
|
+
payload[:data][:attributes][:title] = 'BAD_TITLE'
|
72
|
+
payload[:data][:attributes][:quantity] = 100 + rand(10)
|
73
|
+
payload
|
74
|
+
end
|
75
|
+
|
76
|
+
it do
|
77
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
78
|
+
expect(response_json['errors'].size).to eq(3)
|
79
|
+
expect(response_json['errors'][0]['status']).to eq('422')
|
80
|
+
expect(response_json['errors'][0]['code']).to include('invalid')
|
81
|
+
expect(response_json['errors'][0]['title'])
|
82
|
+
.to eq(Rack::Utils::HTTP_STATUS_CODES[422])
|
83
|
+
expect(response_json['errors'][0]['source'])
|
84
|
+
.to eq('pointer' => '/data/attributes/title')
|
85
|
+
expect(response_json['errors'][0]['detail'])
|
86
|
+
.to eq('Title is invalid')
|
87
|
+
|
88
|
+
expect(response_json['errors'][1]['status']).to eq('422')
|
89
|
+
|
90
|
+
if Rails::VERSION::MAJOR >= 5
|
91
|
+
expect(response_json['errors'][1]['code']).to eq('invalid')
|
92
|
+
else
|
93
|
+
expect(response_json['errors'][1]['code']).to eq('has_typos')
|
94
|
+
end
|
95
|
+
|
96
|
+
expect(response_json['errors'][1]['title'])
|
97
|
+
.to eq(Rack::Utils::HTTP_STATUS_CODES[422])
|
98
|
+
expect(response_json['errors'][1]['source'])
|
99
|
+
.to eq('pointer' => '/data/attributes/title')
|
100
|
+
expect(response_json['errors'][1]['detail'])
|
101
|
+
.to eq('Title has typos')
|
102
|
+
|
103
|
+
expect(response_json['errors'][2]['status']).to eq('422')
|
104
|
+
|
105
|
+
if Rails::VERSION::MAJOR >= 5
|
106
|
+
expect(response_json['errors'][2]['code']).to eq('less_than')
|
107
|
+
else
|
108
|
+
expect(response_json['errors'][2]['code'])
|
109
|
+
.to eq('must_be_less_than_100')
|
110
|
+
end
|
111
|
+
|
112
|
+
expect(response_json['errors'][2]['title'])
|
113
|
+
.to eq(Rack::Utils::HTTP_STATUS_CODES[422])
|
114
|
+
expect(response_json['errors'][2]['source'])
|
115
|
+
.to eq('pointer' => '/data/attributes/quantity')
|
116
|
+
expect(response_json['errors'][2]['detail'])
|
117
|
+
.to eq('Quantity must be less than 100')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'as a param attribute' do
|
122
|
+
let(:params) do
|
123
|
+
payload = note_params.dup
|
124
|
+
payload[:data][:attributes].delete(:title)
|
125
|
+
# To have any attribtues in the payload...
|
126
|
+
payload[:data][:attributes][:created_at] = nil
|
127
|
+
payload
|
128
|
+
end
|
129
|
+
|
130
|
+
it do
|
131
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
132
|
+
expect(response_json['errors'][0]['source'])
|
133
|
+
.to eq('pointer' => '/data/attributes/title')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'with a bad note ID' do
|
139
|
+
let(:user_id) { nil }
|
140
|
+
let(:note_id) { rand(10) }
|
141
|
+
|
142
|
+
it do
|
143
|
+
expect(response).to have_http_status(:not_found)
|
144
|
+
expect(response_json['errors'].size).to eq(1)
|
145
|
+
expect(response_json['errors'][0]['status']).to eq('404')
|
146
|
+
expect(response_json['errors'][0]['title'])
|
147
|
+
.to eq(Rack::Utils::HTTP_STATUS_CODES[404])
|
148
|
+
expect(response_json['errors'][0]['source']).to be_nil
|
149
|
+
expect(response_json['errors'][0]['detail']).to be_nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'with an exception' do
|
154
|
+
let(:user_id) { nil }
|
155
|
+
let(:note_id) { 'tada' }
|
156
|
+
|
157
|
+
it do
|
158
|
+
expect(response).to have_http_status(:internal_server_error)
|
159
|
+
expect(response_json['errors'].size).to eq(1)
|
160
|
+
expect(response_json['errors'][0]['status']).to eq('500')
|
161
|
+
expect(response_json['errors'][0]['title'])
|
162
|
+
.to eq(Rack::Utils::HTTP_STATUS_CODES[500])
|
163
|
+
expect(response_json['errors'][0]['source']).to be_nil
|
164
|
+
expect(response_json['errors'][0]['detail']).to be_nil
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|