Skip to content

Commit f04e84b

Browse files
authored
Merge pull request #4 from art19/tatthurs/merge-art19-patched
Tatthurs/merge art19 patched
2 parents d6982d5 + 3c32d35 commit f04e84b

File tree

4 files changed

+141
-18
lines changed

4 files changed

+141
-18
lines changed

lib/jsonapi/active_model_error_serializer.rb

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,36 @@
33
module JSONAPI
44
# [ActiveModel::Errors] serializer
55
class ActiveModelErrorSerializer < ErrorSerializer
6-
attribute :status do
7-
'422'
6+
class << self
7+
##
8+
# Get the status code to render for the serializer
9+
#
10+
# This considers an optional status provided through the serializer
11+
# parameters, as either a symbol or a number.
12+
#
13+
# @param params [Hash]
14+
# The serializer parameters
15+
#
16+
# @return [Integer]
17+
# The status code to use
18+
def status_code(params)
19+
case params[:status]
20+
when Symbol
21+
Rack::Utils::SYMBOL_TO_STATUS_CODE[params[:status]]
22+
when Integer
23+
params[:status]
24+
else
25+
422
26+
end
27+
end
28+
end
29+
30+
attribute :status do |_, params|
31+
status_code(params).to_s
832
end
933

10-
attribute :title do
11-
Rack::Utils::HTTP_STATUS_CODES[422]
34+
attribute :title do |_, params|
35+
Rack::Utils::HTTP_STATUS_CODES[status_code(params)]
1236
end
1337

1438
attribute :code do |object|
@@ -28,12 +52,12 @@ class ActiveModelErrorSerializer < ErrorSerializer
2852
message = errors_object.generate_message(
2953
error_key, nil, error_hash[:error]
3054
)
31-
elsif error_hash[:error].present?
55+
elsif error_hash[:error].present? && error_hash[:error].is_a?(Symbol)
3256
message = errors_object.generate_message(
3357
error_key, error_hash[:error], error_hash
3458
)
3559
else
36-
message = error_hash[:message]
60+
message = error_hash[:message] || error_hash[:error]
3761
end
3862

3963
errors_object.full_message(error_key, message)
@@ -49,8 +73,10 @@ class ActiveModelErrorSerializer < ErrorSerializer
4973
{ pointer: "/data/attributes/#{error_key}" }
5074
elsif rels.include?(error_key)
5175
{ pointer: "/data/relationships/#{error_key}" }
76+
elsif error_key == :base
77+
{ pointer: '/data' }
5278
else
53-
{ pointer: '' }
79+
{ pointer: nil }
5480
end
5581
end
5682
end

lib/jsonapi/rails.rb

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,22 @@ def self.add_errors_renderer!
6262
details[attr] ||= []
6363
details[attr] << error.detail.merge(message: error.message)
6464
end
65-
elsif resource.respond_to?(:details)
66-
details = resource.details
65+
elsif resource.respond_to?(:details) && resource.respond_to?(:messages)
66+
resource.details.each do |attr, problems|
67+
problems.each_with_index do |error, index|
68+
details[attr] ||= []
69+
70+
if error[:error].is_a?(Hash)
71+
current = error[:error].dup
72+
current[:error] ||= :invalid
73+
74+
details[attr] << current
75+
else
76+
message = resource.messages[attr][index]
77+
details[attr] << error.merge(message: message)
78+
end
79+
end
80+
end
6781
else
6882
details = resource.messages
6983
end
@@ -79,7 +93,11 @@ def self.add_errors_renderer!
7993

8094
JSONAPI::Rails.serializer_to_json(
8195
JSONAPI::ActiveModelErrorSerializer.new(
82-
errors, params: { model: model, model_serializer: model_serializer }
96+
errors, params: {
97+
model: model,
98+
model_serializer: model_serializer,
99+
status: options[:status]
100+
}
83101
)
84102
)
85103
end

spec/dummy.rb

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ def self.ransackable_associations(auth_object = nil)
4343
class Note < ActiveRecord::Base
4444
validates_format_of :title, without: /BAD_TITLE/
4545
validates_numericality_of :quantity, less_than: 100, if: :quantity?
46+
validate :title_check
4647
belongs_to :user, required: true
48+
before_destroy :deletable?
4749

4850
def self.ransackable_associations(auth_object = nil)
4951
%w(user)
@@ -52,6 +54,21 @@ def self.ransackable_associations(auth_object = nil)
5254
def self.ransackable_attributes(auth_object = nil)
5355
%w(created_at id quantity title updated_at user_id)
5456
end
57+
58+
# Provide a validation adding an error to the model's base
59+
def title_check
60+
return unless title == 'n/a'
61+
62+
message = 'The record has an unacceptable title.'
63+
errors.add(:base, :model_invalid, errors: message)
64+
end
65+
66+
def deletable?
67+
return true unless title == 'Lovely'
68+
69+
errors.add(:base, "Can't delete lovely notes")
70+
throw :abort
71+
end
5572
end
5673

5774
class CustomNoteSerializer
@@ -84,7 +101,7 @@ class Dummy < Rails::Application
84101
routes.draw do
85102
scope defaults: { format: :jsonapi } do
86103
resources :users, only: [:index]
87-
resources :notes, only: [:update]
104+
resources :notes, only: [:update, :destroy]
88105
end
89106
end
90107
end
@@ -151,6 +168,15 @@ def update
151168
end
152169
end
153170

171+
def destroy
172+
note = Note.find(params[:id])
173+
if note.destroy
174+
head :no_content
175+
else
176+
render jsonapi_errors: note.errors, status: :conflict
177+
end
178+
end
179+
154180
private
155181
def render_jsonapi_internal_server_error(exception)
156182
Rails.logger.error(exception)

spec/errors_spec.rb

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,8 @@
6161
.to eq(Rack::Utils::HTTP_STATUS_CODES[422])
6262
expect(response_json['errors'][0]['source'])
6363
.to eq('pointer' => '/data/relationships/user')
64-
if Rails.gem_version >= Gem::Version.new('6.1')
65-
expect(response_json['errors'][0]['detail'])
66-
.to eq('User must exist')
67-
else
68-
expect(response_json['errors'][0]['detail'])
69-
.to eq('User can\'t be blank')
70-
end
64+
expect(response_json['errors'][0]['detail'])
65+
.to eq('User must exist')
7166
end
7267

7368
context 'required by validations' do
@@ -138,6 +133,27 @@
138133
.to eq('pointer' => '/data/attributes/title')
139134
end
140135
end
136+
137+
context 'with a validation error on the class' do
138+
let(:params) do
139+
payload = note_params.dup
140+
payload[:data][:attributes][:title] = 'n/a'
141+
payload
142+
end
143+
144+
it do
145+
expect(response).to have_http_status(:unprocessable_entity)
146+
expect(response_json['errors'].size).to eq(1)
147+
expect(response_json['errors'][0]['status']).to eq('422')
148+
expect(response_json['errors'][0]['code']).to include('invalid')
149+
expect(response_json['errors'][0]['title'])
150+
.to eq(Rack::Utils::HTTP_STATUS_CODES[422])
151+
expect(response_json['errors'][0]['source'])
152+
.to eq('pointer' => '/data')
153+
expect(response_json['errors'][0]['detail'])
154+
.to eq('Validation failed: The record has an unacceptable title.')
155+
end
156+
end
141157
end
142158

143159
context 'with a bad note ID' do
@@ -170,4 +186,41 @@
170186
end
171187
end
172188
end
189+
190+
describe 'DELETE /nodes/:id' do
191+
let(:note) { create_note }
192+
let(:note_id) { note.id }
193+
let(:user) { note.user }
194+
let(:user_id) { user.id }
195+
196+
context 'with a random note' do
197+
before { delete(note_path(note_id), headers: jsonapi_headers) }
198+
199+
it { expect(response).to have_http_status(:no_content) }
200+
end
201+
202+
context 'with a lovely note' do
203+
let(:errors) do
204+
{
205+
'errors' => [
206+
{
207+
'code' => 'cant_delete_lovely_notes',
208+
'detail' => "Can't delete lovely notes",
209+
'source' => { 'pointer' => '/data' },
210+
'status' => '409',
211+
'title' => 'Conflict'
212+
}
213+
]
214+
}
215+
end
216+
217+
before do
218+
note.update(title: 'Lovely')
219+
delete(note_path(note_id), headers: jsonapi_headers)
220+
end
221+
222+
it { expect(response).to have_http_status(:conflict) }
223+
it { expect(response_json).to match(errors) }
224+
end
225+
end
173226
end

0 commit comments

Comments
 (0)