Skip to content

Commit 3c5e11b

Browse files
authored
Merge pull request #2216 from rails-api/serialize_resource_with_nil_id
Fix: Serialize resource type for unpersisted records (blank id)
2 parents 33ec26f + 51f2643 commit 3c5e11b

File tree

4 files changed

+187
-153
lines changed

4 files changed

+187
-153
lines changed

lib/active_model_serializers/adapter/json_api/resource_identifier.rb

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ def self.type_for(serializer, serializer_type = nil, transform_options = {})
88
end
99

1010
def self.for_type_with_id(type, id, options)
11-
return nil if id.blank?
1211
type = inflect_type(type)
13-
{
14-
id: id.to_s,
15-
type: type_for(:no_class_needed, type, options)
16-
}
12+
type = type_for(:no_class_needed, type, options)
13+
if id.blank?
14+
{ type: type }
15+
else
16+
{ id: id.to_s, type: type }
17+
end
1718
end
1819

1920
def self.raw_type_from_serializer_object(object)
@@ -38,8 +39,11 @@ def initialize(serializer, options)
3839
end
3940

4041
def as_json
41-
return nil if id.blank?
42-
{ id: id, type: type }
42+
if id.blank?
43+
{ type: type }
44+
else
45+
{ id: id.to_s, type: type }
46+
end
4347
end
4448

4549
protected

test/adapter/json_api/resource_identifier_test.rb

Lines changed: 0 additions & 110 deletions
This file was deleted.

test/adapter/json_api/type_test.rb

Lines changed: 168 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,192 @@
11
require 'test_helper'
22

3-
module ActiveModel
4-
class Serializer
5-
module Adapter
6-
class JsonApi
7-
class TypeTest < ActiveSupport::TestCase
8-
class StringTypeSerializer < ActiveModel::Serializer
9-
attribute :name
10-
type 'profile'
3+
module ActiveModelSerializers
4+
module Adapter
5+
class JsonApi
6+
class TypeTest < ActiveSupport::TestCase
7+
class StringTypeSerializer < ActiveModel::Serializer
8+
attribute :name
9+
type 'profile'
10+
end
11+
12+
class SymbolTypeSerializer < ActiveModel::Serializer
13+
attribute :name
14+
type :profile
15+
end
16+
17+
setup do
18+
@author = Author.new(id: 1, name: 'Steve K.')
19+
end
20+
21+
def test_config_plural
22+
with_jsonapi_inflection :plural do
23+
assert_type(@author, 'authors')
1124
end
25+
end
1226

13-
class SymbolTypeSerializer < ActiveModel::Serializer
14-
attribute :name
15-
type :profile
27+
def test_config_singular
28+
with_jsonapi_inflection :singular do
29+
assert_type(@author, 'author')
1630
end
31+
end
32+
33+
def test_explicit_string_type_value
34+
assert_type(@author, 'profile', serializer: StringTypeSerializer)
35+
end
36+
37+
def test_explicit_symbol_type_value
38+
assert_type(@author, 'profile', serializer: SymbolTypeSerializer)
39+
end
40+
41+
private
1742

18-
setup do
19-
@author = Author.new(id: 1, name: 'Steve K.')
43+
def assert_type(resource, expected_type, opts = {})
44+
opts = opts.reverse_merge(adapter: :json_api)
45+
hash = serializable(resource, opts).serializable_hash
46+
assert_equal(expected_type, hash.fetch(:data).fetch(:type))
47+
end
48+
end
49+
class ResourceIdentifierTest < ActiveSupport::TestCase
50+
class WithDefinedTypeSerializer < ActiveModel::Serializer
51+
type 'with_defined_types'
52+
end
53+
54+
class WithDefinedIdSerializer < ActiveModel::Serializer
55+
def id
56+
'special_id'
2057
end
58+
end
59+
60+
class FragmentedSerializer < ActiveModel::Serializer
61+
cache only: :id
2162

22-
def test_config_plural
23-
with_jsonapi_resource_type :plural do
24-
assert_type(@author, 'authors')
25-
end
63+
def id
64+
'special_id'
2665
end
66+
end
67+
68+
setup do
69+
@model = Author.new(id: 1, name: 'Steve K.')
70+
ActionController::Base.cache_store.clear
71+
end
2772

28-
def test_config_singular
29-
with_jsonapi_resource_type :singular do
30-
assert_type(@author, 'author')
31-
end
73+
def test_defined_type
74+
actual = with_jsonapi_inflection :plural do
75+
actual_resource_identifier_object(WithDefinedTypeSerializer, @model)
3276
end
77+
expected = { id: expected_model_id(@model), type: 'with-defined-types' }
78+
assert_equal actual, expected
79+
end
3380

34-
def test_explicit_string_type_value
35-
assert_type(@author, 'profile', serializer: StringTypeSerializer)
81+
def test_defined_type_not_inflected
82+
actual = with_jsonapi_inflection :singular do
83+
actual_resource_identifier_object(WithDefinedTypeSerializer, @model)
3684
end
85+
expected = { id: expected_model_id(@model), type: 'with-defined-types' }
86+
assert_equal actual, expected
87+
end
3788

38-
def test_explicit_symbol_type_value
39-
assert_type(@author, 'profile', serializer: SymbolTypeSerializer)
89+
def test_singular_type
90+
actual = with_jsonapi_inflection :singular do
91+
actual_resource_identifier_object(AuthorSerializer, @model)
4092
end
93+
expected = { id: expected_model_id(@model), type: 'author' }
94+
assert_equal actual, expected
95+
end
4196

42-
private
97+
def test_plural_type
98+
actual = with_jsonapi_inflection :plural do
99+
actual_resource_identifier_object(AuthorSerializer, @model)
100+
end
101+
expected = { id: expected_model_id(@model), type: 'authors' }
102+
assert_equal actual, expected
103+
end
104+
105+
def test_type_with_namespace
106+
Object.const_set(:Admin, Module.new)
107+
model = Class.new(::Model)
108+
Admin.const_set(:PowerUser, model)
109+
serializer = Class.new(ActiveModel::Serializer)
110+
Admin.const_set(:PowerUserSerializer, serializer)
111+
with_namespace_separator '--' do
112+
admin_user = Admin::PowerUser.new
113+
serializer = Admin::PowerUserSerializer.new(admin_user)
114+
expected = {
115+
id: admin_user.id,
116+
type: 'admin--power-users'
117+
}
118+
119+
identifier = ResourceIdentifier.new(serializer, {})
120+
actual = identifier.as_json
121+
assert_equal(expected, actual)
122+
end
123+
end
124+
125+
def test_id_defined_on_object
126+
actual = actual_resource_identifier_object(AuthorSerializer, @model)
127+
expected = { id: @model.id.to_s, type: expected_model_type(@model) }
128+
assert_equal actual, expected
129+
end
130+
131+
def test_blank_id
132+
model = Author.new(id: nil, name: 'Steve K.')
133+
actual = actual_resource_identifier_object(AuthorSerializer, model)
134+
expected = { type: expected_model_type(model) }
135+
assert_equal actual, expected
136+
end
137+
138+
def test_for_type_with_id
139+
id = 1
140+
actual = ResourceIdentifier.for_type_with_id('admin_user', id, {})
141+
expected = { id: '1', type: 'admin-users' }
142+
assert_equal actual, expected
143+
end
43144

44-
def assert_type(resource, expected_type, opts = {})
45-
opts = opts.reverse_merge(adapter: :json_api)
46-
hash = serializable(resource, opts).serializable_hash
47-
assert_equal(expected_type, hash.fetch(:data).fetch(:type))
145+
def test_for_type_with_id_given_blank_id
146+
id = ''
147+
actual = ResourceIdentifier.for_type_with_id('admin_user', id, {})
148+
expected = { type: 'admin-users' }
149+
assert_equal actual, expected
150+
end
151+
152+
def test_for_type_with_id_inflected
153+
id = 2
154+
actual = with_jsonapi_inflection :singular do
155+
ResourceIdentifier.for_type_with_id('admin_users', id, {})
48156
end
157+
expected = { id: '2', type: 'admin-user' }
158+
assert_equal actual, expected
159+
end
160+
161+
def test_id_defined_on_serializer
162+
actual = actual_resource_identifier_object(WithDefinedIdSerializer, @model)
163+
expected = { id: 'special_id', type: expected_model_type(@model) }
164+
assert_equal actual, expected
165+
end
166+
167+
def test_id_defined_on_fragmented
168+
actual = actual_resource_identifier_object(FragmentedSerializer, @model)
169+
expected = { id: 'special_id', type: expected_model_type(@model) }
170+
assert_equal actual, expected
171+
end
172+
173+
private
174+
175+
def actual_resource_identifier_object(serializer_class, model)
176+
serializer = serializer_class.new(model)
177+
resource_identifier = ResourceIdentifier.new(serializer, nil)
178+
resource_identifier.as_json
179+
end
49180

50-
def with_jsonapi_resource_type(inflection)
51-
old_inflection = ActiveModelSerializers.config.jsonapi_resource_type
52-
ActiveModelSerializers.config.jsonapi_resource_type = inflection
53-
yield
54-
ensure
55-
ActiveModelSerializers.config.jsonapi_resource_type = old_inflection
181+
def expected_model_type(model, inflection = ActiveModelSerializers.config.jsonapi_resource_type)
182+
with_jsonapi_inflection inflection do
183+
model.class.model_name.send(inflection)
56184
end
57185
end
186+
187+
def expected_model_id(model)
188+
model.id.to_s
189+
end
58190
end
59191
end
60192
end

test/support/serialization_testing.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ def with_config(hash)
4747
ActiveModelSerializers.config.replace(old_config)
4848
end
4949

50+
def with_jsonapi_inflection(inflection)
51+
original_inflection = ActiveModelSerializers.config.jsonapi_resource_type
52+
ActiveModelSerializers.config.jsonapi_resource_type = inflection
53+
yield
54+
ensure
55+
ActiveModelSerializers.config.jsonapi_resource_type = original_inflection
56+
end
57+
5058
def with_serializer_lookup_disabled
5159
original_serializer_lookup = ActiveModelSerializers.config.serializer_lookup_enabled
5260
ActiveModelSerializers.config.serializer_lookup_enabled = false

0 commit comments

Comments
 (0)