Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion lib/jsonapi/config.ex
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
defmodule JSONAPI.Config do
@moduledoc """
Configuration struct containing JSON API information for a request

Much of the data in this struct is populated for you by various Plugs this
library offers if you choose to use them.

`includes_post_processor`, if nil, will default to running all includes
through `Enum.uniq/1`. You can customize this behavior if needed with a
function that accepts two arguments: The includes about to be seriailzed and
the requested includes for the current request. Your function must return the
includes as you want them to be serialized.
"""

defstruct data: nil,
fields: %{},
filter: [],
include: [],
includes_post_processor: nil,
opts: nil,
sort: nil,
view: nil,
page: %{}

@type requested_include :: atom | {atom, any}

@type t :: %__MODULE__{
data: nil | map,
fields: map,
filter: keyword,
include: [atom | {atom, any}],
include: [requested_include],
includes_post_processor: nil | (keyword(), [requested_include] -> keyword()),
opts: nil | keyword,
sort: nil | keyword,
view: any,
Expand Down
26 changes: 18 additions & 8 deletions lib/jsonapi/serializer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,30 @@ defmodule JSONAPI.Serializer do
@spec serialize(View.t(), View.data(), Conn.t() | nil, View.meta() | nil, View.options()) ::
document()
def serialize(view, data, conn \\ nil, meta \\ nil, options \\ []) do
{query_includes, query_page} =
{query_includes, query_page, includes_post_processor} =
case conn do
%Conn{assigns: %{jsonapi_query: %Config{include: include, page: page}}} ->
{include, page}
%Conn{
assigns: %{
jsonapi_query: %Config{include: include, page: page, includes_post_processor: includes_post_processor}
}
} ->
{include, page, includes_post_processor}

_ ->
{[], nil}
{[], nil, nil}
end

{to_include, encoded_data} = encode_data(view, data, conn, query_includes, options)

post_process_includes =
case includes_post_processor do
nil -> &Enum.uniq/1
process -> &process.(&1, query_includes)
end

encoded_data = %{
data: encoded_data,
included: flatten_included(to_include)
included: flatten_included(to_include, post_process_includes)
}

encoded_data =
Expand Down Expand Up @@ -296,12 +306,12 @@ defmodule JSONAPI.Serializer do
end

# Flatten and unique all the included objects
@spec flatten_included(keyword()) :: keyword()
def flatten_included(included) do
@spec flatten_included(keyword(), (keyword() -> keyword())) :: keyword()
def flatten_included(included, post_process) do
included
|> List.flatten()
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
|> post_process.()
end

defp assoc_loaded?(nil), do: serialize_nil_relationships?()
Expand Down
59 changes: 59 additions & 0 deletions test/jsonapi/serializer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,65 @@ defmodule JSONAPI.SerializerTest do
assert Enum.count(encoded.included) == 4
end

test "serialize uses a custom include post-processing function if provided" do
data = %{
id: 1,
username: "jim",
first_name: "Jimmy",
last_name: "Beam",
company: %{id: 2, name: "acme", industry: %{id: 4, name: "stuff"}}
}

conn =
Plug.Conn.fetch_query_params(%Plug.Conn{
assigns: %{
jsonapi_query: %Config{
include: [company: :industry],
includes_post_processor: fn included, requested ->
assert requested == [company: :industry]
Enum.filter(included, fn i -> i[:id] != "2" end)
end
}
}
})

encoded = Serializer.serialize(UserView, data, conn)

assert Enum.count(encoded.included) == 1
end

test "serialize deduplicates includes by default" do
data = [
%{
id: 1,
username: "jim",
first_name: "Jimmy",
last_name: "Beam",
company: %{id: 2, name: "acme", industry: %{id: 4, name: "stuff"}}
},
%{
id: 2,
username: "jim",
first_name: "Jimmy",
last_name: "Beam",
company: %{id: 3, name: "globex", industry: %{id: 4, name: "stuff"}}
}
]

conn =
Plug.Conn.fetch_query_params(%Plug.Conn{
assigns: %{
jsonapi_query: %Config{
include: [company: :industry]
}
}
})

encoded = Serializer.serialize(UserView, data, conn)

assert Enum.count(encoded.included) == 3
end

test "includes from the query when not included by default" do
data = %{
id: 1,
Expand Down
Loading