Skip to content

Commit 9d05d83

Browse files
committed
add backwards compatible polymorphic resource support
1 parent bc95701 commit 9d05d83

File tree

4 files changed

+165
-22
lines changed

4 files changed

+165
-22
lines changed

lib/jsonapi/serializer.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ defmodule JSONAPI.Serializer do
6262

6363
encoded_data = %{
6464
id: view.id(data),
65-
type: view.type(),
65+
type: view.resource_type(data),
6666
attributes: transform_fields(view.attributes(data, conn)),
6767
relationships: %{}
6868
}
@@ -255,7 +255,7 @@ defmodule JSONAPI.Serializer do
255255

256256
def encode_rel_data(view, data) do
257257
%{
258-
type: view.type(),
258+
type: view.resource_type(data),
259259
id: view.id(data)
260260
}
261261
end

lib/jsonapi/view.ex

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,12 @@ defmodule JSONAPI.View do
140140
@type options :: keyword()
141141
@type resource_id :: String.t()
142142
@type resource_type :: String.t()
143+
@type polymorphic_resource :: boolean()
143144

144145
@callback attributes(data(), Conn.t() | nil) :: map()
145146
@callback id(data()) :: resource_id() | nil
146147
@callback fields() :: [field()]
148+
@callback polymorphic_fields(data()) :: [field()]
147149
@callback get_field(field(), data(), Conn.t()) :: any()
148150
@callback hidden(data()) :: [field()]
149151
@callback links(data(), Conn.t()) :: links()
@@ -156,6 +158,7 @@ defmodule JSONAPI.View do
156158
{atom(), t() | {t(), :include} | {atom(), t()} | {atom(), t(), :include}}
157159
]
158160
@callback type() :: resource_type()
161+
@callback polymorphic_type(data()) :: resource_type()
159162
@callback url_for(data(), Conn.t() | nil) :: String.t()
160163
@callback url_for_pagination(data(), Conn.t(), Paginator.params()) :: String.t()
161164
@callback url_for_rel(term(), String.t(), Conn.t() | nil) :: String.t()
@@ -167,7 +170,8 @@ defmodule JSONAPI.View do
167170
{type, opts} = Keyword.pop(opts, :type)
168171
{namespace, opts} = Keyword.pop(opts, :namespace)
169172
{path, opts} = Keyword.pop(opts, :path)
170-
{paginator, _opts} = Keyword.pop(opts, :paginator)
173+
{paginator, opts} = Keyword.pop(opts, :paginator)
174+
{polymorphic_resource, _opts} = Keyword.pop(opts, :polymorphic_resource, false)
171175

172176
quote do
173177
alias JSONAPI.{Serializer, View}
@@ -178,6 +182,7 @@ defmodule JSONAPI.View do
178182
@namespace unquote(namespace)
179183
@path unquote(path)
180184
@paginator unquote(paginator)
185+
@polymorphic_resource unquote(polymorphic_resource)
181186

182187
@impl View
183188
def id(nil), do: nil
@@ -205,8 +210,21 @@ defmodule JSONAPI.View do
205210
end)
206211
end
207212

208-
@impl View
209-
def fields, do: raise("Need to implement fields/0")
213+
cond do
214+
!@polymorphic_resource ->
215+
@impl View
216+
def fields, do: raise("Need to implement fields/0")
217+
218+
@impl View
219+
def polymorphic_fields(_data), do: nil
220+
221+
@polymorphic_resource ->
222+
@impl View
223+
def fields, do: nil
224+
225+
@impl View
226+
def polymorphic_fields(_data), do: raise("Need to implement polymorphic_fields/1")
227+
end
210228

211229
@impl View
212230
def hidden(_data), do: []
@@ -241,11 +259,27 @@ defmodule JSONAPI.View do
241259
@impl View
242260
def relationships, do: []
243261

244-
@impl View
245-
if @resource_type do
246-
def type, do: @resource_type
247-
else
248-
def type, do: raise("Need to implement type/0")
262+
cond do
263+
@resource_type ->
264+
@impl View
265+
def type, do: @resource_type
266+
267+
@impl View
268+
def polymorphic_type(_data), do: nil
269+
270+
!@polymorphic_resource ->
271+
@impl View
272+
def type, do: raise("Need to implement type/0")
273+
274+
@impl View
275+
def polymorphic_type(_data), do: nil
276+
277+
@polymorphic_resource ->
278+
@impl View
279+
def type, do: nil
280+
281+
@impl View
282+
def polymorphic_type(_data), do: raise("Need to implement polymorphic_type/1")
249283
end
250284

251285
@impl View
@@ -264,6 +298,22 @@ defmodule JSONAPI.View do
264298
def visible_fields(data, conn),
265299
do: View.visible_fields(__MODULE__, data, conn)
266300

301+
def resource_fields(data) do
302+
if @polymorphic_resource do
303+
polymorphic_fields(data)
304+
else
305+
fields()
306+
end
307+
end
308+
309+
def resource_type(data) do
310+
if @polymorphic_resource do
311+
polymorphic_type(data)
312+
else
313+
type()
314+
end
315+
end
316+
267317
defoverridable View
268318

269319
def index(models, conn, _params, meta \\ nil, options \\ []),
@@ -336,11 +386,11 @@ defmodule JSONAPI.View do
336386

337387
@spec url_for(t(), term(), Conn.t() | nil) :: String.t()
338388
def url_for(view, data, nil = _conn) when is_nil(data) or is_list(data),
339-
do: URI.to_string(%URI{path: Enum.join([view.namespace(), path_for(view)], "/")})
389+
do: URI.to_string(%URI{path: Enum.join([view.namespace(), path_for(view, data)], "/")})
340390

341391
def url_for(view, data, nil = _conn) do
342392
URI.to_string(%URI{
343-
path: Enum.join([view.namespace(), path_for(view), view.id(data)], "/")
393+
path: Enum.join([view.namespace(), path_for(view, data), view.id(data)], "/")
344394
})
345395
end
346396

@@ -349,7 +399,7 @@ defmodule JSONAPI.View do
349399
scheme: scheme(conn),
350400
host: host(conn),
351401
port: port(conn),
352-
path: Enum.join([view.namespace(), path_for(view)], "/")
402+
path: Enum.join([view.namespace(), path_for(view, data)], "/")
353403
})
354404
end
355405

@@ -358,7 +408,7 @@ defmodule JSONAPI.View do
358408
scheme: scheme(conn),
359409
host: host(conn),
360410
port: port(conn),
361-
path: Enum.join([view.namespace(), path_for(view), view.id(data)], "/")
411+
path: Enum.join([view.namespace(), path_for(view, data), view.id(data)], "/")
362412
})
363413
end
364414

@@ -392,8 +442,8 @@ defmodule JSONAPI.View do
392442
def visible_fields(view, data, conn) do
393443
all_fields =
394444
view
395-
|> requested_fields_for_type(conn)
396-
|> net_fields_for_type(view.fields())
445+
|> requested_fields_for_type(data, conn)
446+
|> net_fields_for_type(view.resource_fields(data))
397447

398448
hidden_fields = view.hidden(data)
399449

@@ -420,11 +470,11 @@ defmodule JSONAPI.View do
420470
|> URI.to_string()
421471
end
422472

423-
defp requested_fields_for_type(view, %Conn{assigns: %{jsonapi_query: %{fields: fields}}}) do
424-
fields[view.type()]
473+
defp requested_fields_for_type(view, data, %Conn{assigns: %{jsonapi_query: %{fields: fields}}}) do
474+
fields[view.resource_type(data)]
425475
end
426476

427-
defp requested_fields_for_type(_view, _conn), do: nil
477+
defp requested_fields_for_type(_view, _data, _conn), do: nil
428478

429479
defp host(%Conn{host: host}),
430480
do: Application.get_env(:jsonapi, :host, host)
@@ -438,5 +488,5 @@ defmodule JSONAPI.View do
438488
defp scheme(%Conn{scheme: scheme}),
439489
do: Application.get_env(:jsonapi, :scheme, to_string(scheme))
440490

441-
defp path_for(view), do: view.path() || view.type()
491+
defp path_for(view, data), do: view.path() || view.resource_type(data)
442492
end

test/jsonapi/serializer_test.exs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ defmodule JSONAPI.SerializerTest do
1313
def relationships do
1414
[
1515
author: {JSONAPI.SerializerTest.UserView, :include},
16-
best_comments: {JSONAPI.SerializerTest.CommentView, :include}
16+
best_comments: {JSONAPI.SerializerTest.CommentView, :include},
17+
polymorphics: {JSONAPI.SerializerTest.PolymorphicView, :include}
1718
]
1819
end
1920
end
@@ -183,6 +184,38 @@ defmodule JSONAPI.SerializerTest do
183184
end
184185
end
185186

187+
defmodule PolymorphicDataOne do
188+
defstruct [:id, :some_field]
189+
end
190+
191+
defmodule PolymorphicDataTwo do
192+
defstruct [:id, :some_other_field]
193+
end
194+
195+
defmodule PolymorphicView do
196+
use JSONAPI.View, polymorphic_resource: true
197+
198+
def polymorphic_type(data) do
199+
case data do
200+
%PolymorphicDataOne{} ->
201+
"polymorphic_data_one"
202+
203+
%PolymorphicDataTwo{} ->
204+
"polymorphic_data_one"
205+
end
206+
end
207+
208+
def polymorphic_fields(data) do
209+
case data do
210+
%PolymorphicDataOne{} ->
211+
[:some_field]
212+
213+
%PolymorphicDataTwo{} ->
214+
[:some_other_field]
215+
end
216+
end
217+
end
218+
186219
setup do
187220
Application.put_env(:jsonapi, :field_transformation, :underscore)
188221

@@ -219,6 +252,10 @@ defmodule JSONAPI.SerializerTest do
219252
best_comments: [
220253
%{id: 5, text: "greatest comment ever", user: %{id: 4, username: "jack"}},
221254
%{id: 6, text: "not so great", user: %{id: 2, username: "jason"}}
255+
],
256+
polymorphic: [
257+
%PolymorphicDataOne{id: 1, some_field: "foo"},
258+
%PolymorphicDataTwo{id: 2, some_other_field: "foo"}
222259
]
223260
}
224261

@@ -842,7 +879,8 @@ defmodule JSONAPI.SerializerTest do
842879

843880
assert configs == [
844881
{:author, :author, JSONAPI.SerializerTest.UserView, true},
845-
{:best_comments, :best_comments, JSONAPI.SerializerTest.CommentView, true}
882+
{:best_comments, :best_comments, JSONAPI.SerializerTest.CommentView, true},
883+
{:polymorphics, :polymorphics, JSONAPI.SerializerTest.PolymorphicView, true}
846884
]
847885
end
848886

test/jsonapi/view_test.exs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,38 @@ defmodule JSONAPI.ViewTest do
5555
def get_field(field, _data, _conn), do: "#{field}!"
5656
end
5757

58+
defmodule PolymorphicDataOne do
59+
defstruct [:some_field]
60+
end
61+
62+
defmodule PolymorphicDataTwo do
63+
defstruct [:some_other_field]
64+
end
65+
66+
defmodule PolymorphicView do
67+
use JSONAPI.View, polymorphic_resource: true
68+
69+
def polymorphic_type(data) do
70+
case data do
71+
%PolymorphicDataOne{} ->
72+
"polymorphic_data_one"
73+
74+
%PolymorphicDataTwo{} ->
75+
"polymorphic_data_one"
76+
end
77+
end
78+
79+
def polymorphic_fields(data) do
80+
case data do
81+
%PolymorphicDataOne{} ->
82+
[:some_field]
83+
84+
%PolymorphicDataTwo{} ->
85+
[:some_other_field]
86+
end
87+
end
88+
end
89+
5890
setup do
5991
Application.put_env(:jsonapi, :field_transformation, :underscore)
6092
Application.put_env(:jsonapi, :namespace, "/other-api")
@@ -71,6 +103,20 @@ defmodule JSONAPI.ViewTest do
71103
assert PostView.type() == "posts"
72104
end
73105

106+
describe "resource_type/1" do
107+
test "equals result of type/0 if resource is not polymorphic" do
108+
assert PostView.type() == PostView.resource_type(%{})
109+
end
110+
111+
test "equals result of polymorphic_type/1 if resource is polymorphic" do
112+
assert PolymorphicView.polymorphic_type(%PolymorphicDataOne{}) ==
113+
PolymorphicView.resource_type(%PolymorphicDataOne{})
114+
115+
assert PolymorphicView.polymorphic_type(%PolymorphicDataTwo{}) ==
116+
PolymorphicView.resource_type(%PolymorphicDataTwo{})
117+
end
118+
end
119+
74120
describe "namespace/0" do
75121
setup do
76122
Application.put_env(:jsonapi, :namespace, "/cake")
@@ -314,4 +360,13 @@ defmodule JSONAPI.ViewTest do
314360
static_fun: "static_fun/2"
315361
} == DynamicView.attributes(data, conn)
316362
end
363+
364+
test "attributes/2 can return polymorphic fields" do
365+
data = %PolymorphicDataTwo{some_other_field: "foo"}
366+
conn = %Plug.Conn{assigns: %{jsonapi_query: %JSONAPI.Config{}}}
367+
368+
assert %{
369+
some_other_field: "foo"
370+
} == PolymorphicView.attributes(data, conn)
371+
end
317372
end

0 commit comments

Comments
 (0)