jsonapi.rb 1.6.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|