Skip to content

Commit 8171321

Browse files
committed
Allow save to return false and translates to an error
Allows an ActiveRecord callback to cancel save. This will result in a `SaveFailed` error.
1 parent 515980b commit 8171321

6 files changed

Lines changed: 67 additions & 5 deletions

File tree

lib/jsonapi/error_codes.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module JSONAPI
1919
INVALID_PAGE_VALUE = 118
2020
INVALID_FIELD_FORMAT = 119
2121
INVALID_FILTERS_SYNTAX = 120
22+
SAVE_FAILED = 121
2223
FORBIDDEN = 403
2324
RECORD_NOT_FOUND = 404
2425
UNSUPPORTED_MEDIA_TYPE = 415
@@ -44,9 +45,11 @@ module JSONAPI
4445
INVALID_PAGE_OBJECT => 'INVALID_PAGE_OBJECT',
4546
INVALID_PAGE_VALUE => 'INVALID_PAGE_VALUE',
4647
INVALID_FIELD_FORMAT => 'INVALID_FIELD_FORMAT',
48+
INVALID_FILTERS_SYNTAX => 'INVALID_FILTERS_SYNTAX',
49+
SAVE_FAILED => 'SAVE_FAILED',
4750
FORBIDDEN => 'FORBIDDEN',
4851
RECORD_NOT_FOUND => 'RECORD_NOT_FOUND',
4952
UNSUPPORTED_MEDIA_TYPE => 'UNSUPPORTED_MEDIA_TYPE',
50-
LOCKED => 'LOCKED',
51-
INVALID_FILTERS_SYNTAX => 'INVALID_FILTERS_SYNTAX' }
53+
LOCKED => 'LOCKED'
54+
}
5255
end

lib/jsonapi/exceptions.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,15 @@ def errors
305305
end
306306
end
307307

308+
class SaveFailed < Error
309+
def errors
310+
[JSONAPI::Error.new(code: JSONAPI::SAVE_FAILED,
311+
status: :unprocessable_entity,
312+
title: 'Save failed or was cancelled',
313+
detail: 'Save failed or was cancelled')]
314+
end
315+
end
316+
308317
class InvalidPageObject < Error
309318
def errors
310319
[JSONAPI::Error.new(code: JSONAPI::INVALID_PAGE_OBJECT,

lib/jsonapi/resource.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ def _save
124124

125125
if defined? @model.save
126126
saved = @model.save
127+
unless saved
128+
raise JSONAPI::Exceptions::SaveFailed.new
129+
end
127130
else
128131
saved = true
129132
end

test/controllers/controller_test.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2322,3 +2322,40 @@ def test_index_default_filter_override
23222322
assert_equal 4, json_response['data'].size
23232323
end
23242324
end
2325+
2326+
class Api::V1::PlanetsControllerTest < ActionController::TestCase
2327+
def test_save_model_callbacks
2328+
set_content_type_header!
2329+
post :create,
2330+
{
2331+
data: {
2332+
type: 'planets',
2333+
attributes: {
2334+
name: 'Zeus',
2335+
description: 'The largest planet in the solar system. Discovered in 2015.'
2336+
}
2337+
}
2338+
}
2339+
2340+
assert_response :created
2341+
assert json_response['data'].is_a?(Hash)
2342+
assert_equal 'Zeus', json_response['data']['attributes']['name']
2343+
end
2344+
2345+
def test_save_model_callbacks_fail
2346+
set_content_type_header!
2347+
post :create,
2348+
{
2349+
data: {
2350+
type: 'planets',
2351+
attributes: {
2352+
name: 'Pluto',
2353+
description: 'Yes, it is a planet.'
2354+
}
2355+
}
2356+
}
2357+
2358+
assert_response :unprocessable_entity
2359+
assert_match /Save failed or was cancelled/, json_response['errors'][0]['detail']
2360+
end
2361+
end

test/fixtures/active_record.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,16 @@ class Planet < ActiveRecord::Base
222222
belongs_to :planet_type
223223

224224
has_and_belongs_to_many :tags, join_table: :planets_tags
225+
226+
# Test model callback cancelling save
227+
before_save :check_not_pluto
228+
229+
def check_not_pluto
230+
# Pluto can't be a planet, so cancel the save
231+
if name.downcase == 'pluto'
232+
return false
233+
end
234+
end
225235
end
226236

227237
class PlanetType < ActiveRecord::Base
@@ -969,7 +979,7 @@ class BadlyNamedAttributesResource < JSONAPI::Resource
969979
description: 'Saturn is the sixth planet from the Sun and the second largest planet in the Solar System, after Jupiter.',
970980
planet_type_id: planetoid.id)
971981
titan = Moon.create(name:'Titan', description: 'Best known of the Saturn moons.', planet_id: saturn.id)
972-
pluto = Planet.create(name: 'Pluto', description: 'Pluto is the smallest planet.', planet_type_id: planetoid.id)
982+
makemake = Planet.create(name: 'Makemake', description: 'A small planetoid in the Kuiperbelt.', planet_type_id: planetoid.id)
973983
uranus = Planet.create(name: 'Uranus', description: 'Insert adolescent jokes here.', planet_type_id: gas_giant.id)
974984
jupiter = Planet.create(name: 'Jupiter', description: 'A gas giant.', planet_type_id: gas_giant.id)
975985
betax = Planet.create(name: 'Beta X', description: 'Newly discovered Planet X', planet_type_id: unknown.id)

test/unit/operation/operations_processor_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,8 @@ def test_remove_resource
304304
op = JSONAPI::OperationsProcessor.new
305305

306306
count = Planet.count
307-
pluto = Planet.find(2)
308-
assert_equal(pluto.name, 'Pluto')
307+
makemake = Planet.find(2)
308+
assert_equal(makemake.name, 'Makemake')
309309

310310
operations = [
311311
JSONAPI::RemoveResourceOperation.new(PlanetResource, resource_id: 2),

0 commit comments

Comments
 (0)