Alembic v4.0.0 Alembic.Document View Source
JSON API refers to the top-level JSON structure as a document.
Link to this section Summary
Functions
Tries to determine the common Alembic.Error.t
status
between all errors
in the document
Converts the errors
in ecto_changeset
to Alembic.Error.t
in a single t
Converts a JSON object into a JSON API Document, t
Lookup table of included
resources, so that Alembic.ResourceIdentifier.t
can be
converted to full Alembic.Resource.t
Merges the errors from two documents together
Since merge/2
adds the second errors
to the beginning of a first
document’s errors
list, the final merged
errors
needs to be reversed to maintain the original order
Converts a document into one or more Ecto.Schema.t
structs
Call to_ecto_schema/2
instead to automatically generate attributes_by_id_by_type
Extract paged pagination information
Transforms a t
into the nested params format used by
Ecto.Changeset.cast/4
Transforms a t
into the nested params format used by
Ecto.Changeset.cast/4
using the given
resources_by_id_by_type
Transforms a t
into the nested params format used by
Ecto.Changeset.cast/4
using the given
resources_by_id_by_type
and converted_by_id_by_type
Link to this section Types
t() :: %Alembic.Document{ data: :unset, errors: [Alembic.Error.t()], included: nil, jsonapi: term(), links: Alembic.Links.t() | nil, meta: Alembic.Meta.t() | nil } | %Alembic.Document{ data: :unset, errors: nil, included: nil, jsonapi: term(), links: Alembic.Links.t() | nil, meta: Alembic.Meta.t() } | %Alembic.Document{ data: [Alembic.Resource.t()] | Alembic.Resource.t() | nil, errors: nil, included: [Alembic.Resource.t()] | nil, jsonapi: term(), links: Alembic.Links.t() | nil, meta: Alembic.Meta.t() | nil }
A JSON API Document.
Data
When there are no errors, data
are returned in the document and errors
are not returned in the document.
Field | Included/Excluded/Optional |
---|---|
data | Included |
errors | Excluded |
included | Optional |
links | Optional |
meta | Optional |
Errors
When an error occurs, errors
are returned in the document and data
are not returned in the document.
Field | Included/Excluded/Optional |
---|---|
data | Excluded |
errors | Included |
included | Excluded |
links | Optional |
meta | Optional |
Meta
JSON API allows a meta
only document, in which case data
and errors
are not returned in the document.
Field | Included/Excluded/Optional |
---|---|
data | Excluded |
errors | Excluded |
included | Excluded |
links | Optional |
meta | Included |
Link to this section Functions
Tries to determine the common Alembic.Error.t
status
between all errors
in the document
.
If it is not an errors document, nil
is returned.
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{data: []}
...> )
nil
Single error
If there is one error, its status is returned. This could be nil as no field is required in a JSONAPI error.
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> status: "404"
...> }
...> ]
...> }
...> )
"404"
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{}
...> ]
...> }
...> )
nil
Multiple errors
If there are multiple errors with the same status, then that is the consensus
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> status: "404"
...> },
...> %Alembic.Error{
...> status: "404"
...> }
...> ]
...> }
...> )
"404"
If there are multiple errors, but some errors don’t have statuses, they are ignored
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{},
...> %Alembic.Error{
...> status: "404"
...> }
...> ]
...> }
...> )
"404"
If there are multiple errors, but they disagree within the same 100s block, then that is the consensus
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> status: "404"
...> },
...> %Alembic.Error{
...> status: "422"
...> }
...> ]
...> }
...> )
"400"
If there are multiple errors, but they disagree without the same 100s block, then the greater 100s block is the consensus
iex> Alembic.Document.error_status_consensus(
...> %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> status: "422"
...> },
...> %Alembic.Error{
...> status: "500"
...> }
...> ]
...> }
...> )
"500"
from_ecto_changeset( Ecto.Changeset.t(), Alembic.Source.pointer_path_from_ecto_changeset_error_field_options() ) :: t()
from_ecto_changeset(Ecto.Changeset.t(), %{format_key: (atom() -> String.t())}) :: t()
Converts the errors
in ecto_changeset
to Alembic.Error.t
in a single t
.
If only :format_key
is given in the options
, then the other keys for
Alembic.Source.pointer_path_from_ecto_changeset_error_field_options
will be derived from the ecto_schema_module
of
the changeset
:data
.
iex> format_key = fn key ->
...> key |> Atom.to_string() |> String.replace("_", "-")
...> end
iex> Document.from_ecto_changeset(
...> %Ecto.Changeset{
...> data: %Alembic.TestAuthor{id: 1},
...> errors: [
...> {:name, {"should be at least %{count} character(s)", [count: 2, validation: :length, min: 2]}},
...> {:posts, {"are still associated with this entry", []}}
...> ]
...> },
...> %{format_key: format_key}
...> )
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "name should be at least 2 character(s)",
source: %Alembic.Source{
pointer: "/data/attributes/name"
},
title: "should be at least 2 character(s)"
},
%Alembic.Error{
detail: "posts are still associated with this entry",
source: %Alembic.Source{
pointer: "/data/relationships/posts"
},
title: "are still associated with this entry"
}
]
}
If the ecto_changeset
data
is not an Ecto.Schema.t
struct and __schema__/1
reflection functions are not
supported, then you can bypass the reflection by giving the
Alembic.Source.pointer_path_from_ecto_changeset_error_field_options
explicitly
iex> format_key = fn key ->
...> key |> Atom.to_string() |> String.replace("_", "-")
...> end
iex> Document.from_ecto_changeset(
...> %Ecto.Changeset{
...> data: %{id: 1},
...> errors: [
...> {:name, {"should be at least %{count} character(s)", [count: 2, validation: :length, min: 2]}},
...> {:posts, {"are still associated with this entry", []}}
...> ]
...> },
...> %{
...> association_set: MapSet.new([:posts]),
...> association_by_foreign_key: %{},
...> attribute_set: MapSet.new([:name]),
...> format_key: format_key
...> }
...> )
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "name should be at least 2 character(s)",
source: %Alembic.Source{
pointer: "/data/attributes/name"
},
title: "should be at least 2 character(s)"
},
%Alembic.Error{
detail: "posts are still associated with this entry",
source: %Alembic.Source{
pointer: "/data/relationships/posts"
},
title: "are still associated with this entry"
}
]
}
Converts a JSON object into a JSON API Document, t
.
Data documents
Single
An empty single resource can represented as "data": null
in encoded JSON, so it comes into from_json
as
data: nil
iex> Alembic.Document.from_json(
...> %{ "data" => nil },
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
data: nil
}
}
A present single can be a resource
iex> Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "attributes" => %{
...> "text" => "First Post!"
...> },
...> "id" => "1",
...> "type" => "post"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
data: %Alembic.Resource{
attributes: %{
"text" => "First Post!"
},
id: "1",
type: "post"
}
}
}
… or a present single can be just a resource identifier
iex> Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "id" => "1",
...> "type" => "post"
...> }
...> },
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
data: %Alembic.ResourceIdentifier{
id: "1",
type: "post"
}
}
}
You notice that whether a JSON object in "data"
is treated as a Alembic.Resource.t
or
Alembic.ResourceIdentifier.t
hinges on whether "attributes"
or "relationships"
is present as those
members are only allowed for resources.
Collection
Resources
A collection can be a list of resources
iex> Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "attributes" => %{
...> "text" => "First Post!"
...> },
...> "id" => "1",
...> "relationships" => %{
...> "comments" => %{
...> "data" => [
...> %{
...> "id" => "1",
...> "type" => "comment"
...> }
...> ]
...> }
...> },
...> "type" => "post"
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
data: [
%Alembic.Resource{
attributes: %{
"text" => "First Post!"
},
id: "1",
relationships: %{
"comments" => %Alembic.Relationship{
data: [
%Alembic.ResourceIdentifier{
id: "1",
type: "comment"
}
]
}
},
type: "post"
}
]
}
}
With "relationships"
, a resources collection can optionally have "included"
for the attributes for the resource
identifiers. If "included"
is not given or the "id"
and "type"
for a resource identifier, then the resource
identifier should just be considered a foreign key reference that needs to be fetched with another API query.
iex> Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "attributes" => %{
...> "text" => "First Post!"
...> },
...> "id" => "1",
...> "relationships" => %{
...> "comments" => %{
...> "data" => [
...> %{
...> "id" => "1",
...> "type" => "comment"
...> }
...> ]
...> }
...> },
...> "type" => "post"
...> }
...> ],
...> "included" => [
...> %{
...> "attributes" => %{
...> "text" => "First Comment!"
...> },
...> "id" => "1",
...> "type" => "comment"
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
data: [
%Alembic.Resource{
attributes: %{
"text" => "First Post!"
},
id: "1",
relationships: %{
"comments" => %Alembic.Relationship{
data: [
%Alembic.ResourceIdentifier{
id: "1",
type: "comment"
}
]
}
},
type: "post"
}
],
included: [
%Alembic.Resource{
attributes: %{
"text" => "First Comment!"
},
id: "1",
type: "comment"
}
]
}
}
Resource Identifiers
Or a list of resource identifiers
iex> Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "id" => "1",
...> "type" => "post"
...> }
...> ]
...> },
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
data: [
%Alembic.ResourceIdentifier{
id: "1",
type: "post"
}
]
}
}
Empty
An empty collection can be signified with []
. Because there is no type information, it’s not possible to tell
whether it is an empty list of Alembic.Resource.t
or Alembic.ResourceIdentifier.t
.
iex> Alembic.Document.from_json(
...> %{
...> "data" => []
...> },
...> %Alembic.Error{
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
data: []
}
}
Errors documents
Errors from the sender must have an "errors"
key set to a list of errors.
iex> Alembic.Document.from_json(
...> %{
...> "errors" => [
...> %{
...> "code" => "1",
...> "detail" => "There was an error in data",
...> "id" => "2",
...> "links" => %{
...> "about" => %{
...> "href" => "/errors/2",
...> "meta" => %{
...> "extra" => "about meta"
...> }
...> }
...> },
...> "meta" => %{
...> "extra" => "error meta"
...> },
...> "source" => %{
...> "pointer" => "/data"
...> },
...> "status" => "422",
...> "title" => "There was an error"
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
errors: [
%Alembic.Error{
code: "1",
detail: "There was an error in data",
id: "2",
links: %{
"about" => %Alembic.Link{
href: "/errors/2",
meta: %{
"extra" => "about meta"
}
}
},
meta: %{
"extra" => "error meta"
},
source: %Alembic.Source{
pointer: "/data"
},
status: "422",
title: "There was an error"
}
]
}
}
Error objects MUST be returned as an array keyed by "errors"
in the top level of a JSON API document.
iex> Alembic.Document.from_json(
...> %{"errors" => "Lots of errors"},
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/errors` type is not array",
meta: %{
"type" => "array"
},
source: %Alembic.Source{
pointer: "/errors"
},
status: "422",
title: "Type is wrong"
}
]
}
}
Meta documents
Returned documents can contain just "meta"
with neither "data"
nor "errors"
iex> Alembic.Document.from_json(
...> %{
...> "meta" => %{
...> "copyright" => "2016"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:ok,
%Alembic.Document{
meta: %{
"copyright" => "2016"
}
}
}
Incomplete documents
If neither "errors"
, "data"
, nor "meta"
is present, then the document is invalid and an :error
tuple will be
returned. In the tuple with :error
is an Alembic.Document.t
that is a proper JSONAPI errors document that can
be set back to the sender.
iex> Alembic.Document.from_json(
...> %{},
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "At least one of the following children of `` must be present:\n" <>
"data\n" <>
"errors\n" <>
"meta",
meta: %{
"children" => [
"data",
"errors",
"meta"
]
},
source: %Alembic.Source{
pointer: ""
},
status: "422",
title: "Not enough children"
}
],
}
}
included_resource_by_id_by_type(t()) :: Alembic.ToParams.resource_by_id_by_type()
Lookup table of included
resources, so that Alembic.ResourceIdentifier.t
can be
converted to full Alembic.Resource.t
.
No included resources
With no included resources, an empty map is returned
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "type" => "post",
...> "id" => "1"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
...> Alembic.Document.included_resource_by_id_by_type(document)
%{}
Included resources
With included resources, a nest map is built with the outer layer keyed by the Alembic.Resource.type
,
then the next layer keyed by the Alembic.Resource.id
with the values being the full
Alembic.Resource.t
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "type" => "articles",
...> "id" => "1",
...> "relationships" => %{
...> "author" => %{
...> "data" => %{
...> "type" => "people",
...> "id" => "9"
...> }
...> },
...> "comments" => %{
...> "data" => [
...> %{
...> "type" => "comments",
...> "id" => "5"
...> },
...> %{
...> "type" => "comments",
...> "id" => "12"
...> }
...> ]
...> }
...> }
...> }
...> ],
...> "included" => [
...> %{
...> "type" => "people",
...> "id" => "9",
...> "attributes" => %{
...> "first-name" => "Dan",
...> "last-name" => "Gebhardt",
...> "twitter" => "dgeb"
...> }
...> },
...> %{
...> "type" => "comments",
...> "id" => "5",
...> "attributes" => %{
...> "body" => "First!"
...> },
...> "relationships" => %{
...> "author" => %{
...> "data" => %{
...> "type" => "people",
...> "id" => "2"
...> }
...> }
...> }
...> },
...> %{
...> "type" => "comments",
...> "id" => "12",
...> "attributes" => %{
...> "body" => "I like XML better"
...> },
...> "relationships" => %{
...> "author" => %{
...> "data" => %{
...> "type" => "people",
...> "id" => "9"
...> }
...> }
...> }
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
...> Alembic.Document.included_resource_by_id_by_type(document)
%{
"comments" => %{
"12" => %Alembic.Resource{
attributes: %{
"body" => "I like XML better"
},
id: "12",
relationships: %{
"author" => %Alembic.Relationship{
data: %Alembic.ResourceIdentifier{
id: "9",
type: "people"
}
}
},
type: "comments"
},
"5" => %Alembic.Resource{
attributes: %{
"body" => "First!"
},
id: "5",
relationships: %{
"author" => %Alembic.Relationship{
data: %Alembic.ResourceIdentifier{
id: "2",
type: "people"
}
}
},
type: "comments"
}
},
"people" => %{
"9" => %Alembic.Resource{
attributes: %{
"first-name" => "Dan",
"last-name" => "Gebhardt",
"twitter" => "dgeb"
},
id: "9",
type: "people"
}
}
}
merge( %Alembic.Document{ data: term(), errors: [Alembic.Error.t()], included: term(), jsonapi: term(), links: term(), meta: term() }, %Alembic.Document{ data: term(), errors: [Alembic.Error.t()], included: term(), jsonapi: term(), links: term(), meta: term() } ) :: %Alembic.Document{ data: term(), errors: [Alembic.Error.t()], included: term(), jsonapi: term(), links: term(), meta: term() }
Merges the errors from two documents together.
The errors from the second document are prepended to the errors of the first document so that the errors as a whole
can be reversed with reverse/1
Since merge/2
adds the second errors
to the beginning of a first
document’s errors
list, the final merged
errors
needs to be reversed to maintain the original order.
iex> merged = %Alembic.Document{
...> errors: [
...> %Alembic.Error{
...> detail: "The index `2` of `/data` is not a resource",
...> source: %Alembic.Source{
...> pointer: "/data/2"
...> },
...> title: "Element is not a resource"
...> },
...> %Alembic.Error{
...> detail: "The index `1` of `/data` is not a resource",
...> source: %Alembic.Source{
...> pointer: "/data/1"
...> },
...> title: "Element is not a resource"
...> }
...> ]
...> }
iex> Alembic.Document.reverse(merged)
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "The index `1` of `/data` is not a resource",
source: %Alembic.Source{
pointer: "/data/1"
},
title: "Element is not a resource"
},
%Alembic.Error{
detail: "The index `2` of `/data` is not a resource",
source: %Alembic.Source{
pointer: "/data/2"
},
title: "Element is not a resource"
}
]
}
Converts a document into one or more Ecto.Schema.t
structs.
Parameters
document
- supplies resource attributes and associations (throughincluded
)ecto_schema_module_by_type
- Maps theAlembic.Resource.t
type
String.t
to theEcto.Schema
module to use withEcto.Changeset.cast/4
.
Returns
{:ok, nil}
- an empty single resource{:ok, struct}
- a single resource{:ok, []}
- an empty resource collection{:ok, [struct]}
- a non-empty resource collection{:error, t}
- thedocument
have errors and so won’t be converted to struct(s).
Examples
No resource
No resource has to return nil
for Ecto schema because the type is not present.
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => nil
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_ecto_schema(document, %{})
{:ok, nil}
Single resource
A single resource is converted to a single Ecto schema struct corresponding to the type.
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "attributes" => %{
...> "name" => "Thing 1"
...> },
...> "id" => "1",
...> "type" => "thing"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_ecto_schema(
...> document,
...> %{
...> "thing" => Alembic.TestThing
...> }
...> )
{
:ok,
%Alembic.TestThing{
id: 1,
name: "Thing 1",
shirt_id: nil
}
}
Relationships
Relationships are merged into the struct for the resource
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "attributes" => %{
...> "name" => "Thing 1"
...> },
...> "relationships" => %{
...> "shirt" => %{
...> "data" => %{
...> "attributes" => %{
...> "size" => "L"
...> },
...> "type" => "shirt"
...> }
...> }
...> },
...> "type" => "thing"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_ecto_schema(
...> document,
...> %{
...> "shirt" => Alembic.TestShirt,
...> "thing" => Alembic.TestThing
...> }
...> )
{:ok, %Alembic.TestThing{name: "Thing 1", shirt: %Alembic.TestShirt{size: "L"}}}
Multiple resources
Multiple resources are converted to a struct list where each element is a struct that combines the id and attributes
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "type" => "post",
...> "id" => "1",
...> "attributes" => %{
...> "text" => "Welcome"
...> }
...> },
...> %{
...> "type" => "post",
...> "id" => "2",
...> "attributes" => %{
...> "text" => "It's been awhile"
...> }
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_ecto_schema(
...> document,
...> %{
...> "post" => Alembic.TestPost
...> }
...> )
{
:ok,
[
%Alembic.TestPost{author_id: nil, id: 1, text: "Welcome"},
%Alembic.TestPost{author_id: nil, id: 2, text: "It's been awhile"},
]
}
Relationships
Relationships are merged into the structs for the corresponding resource
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "type" => "post",
...> "id" => "1",
...> "attributes" => %{
...> "text" => "Welcome"
...> },
...> "relationships" => %{
...> "comments" => %{
...> "data" => [
...> %{
...> "type" => "comment",
...> "id" => "3"
...> }
...> ]
...> }
...> }
...> },
...> %{
...> "type" => "post",
...> "id" => "2",
...> "attributes" => %{
...> "text" => "It's been awhile"
...> },
...> "relationships" => %{
...> "comments" => %{
...> "data" => []
...> }
...> }
...> }
...> ],
...> "included" => [
...> %{
...> "type" => "comment",
...> "id" => "3",
...> "attributes" => %{
...> "text" => "First!"
...> }
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_ecto_schema(
...> document,
...> %{
...> "comment" => Alembic.TestComment,
...> "post" => Alembic.TestPost
...> }
...> )
{
:ok,
[
%Alembic.TestPost{
author_id: nil,
comments: [
%Alembic.TestComment{
id: 3,
post_id: nil,
text: "First!"
}
],
id: 1,
text: "Welcome"
},
%Alembic.TestPost{
author_id: nil,
comments: [],
id: 2,
text: "It's been awhile"
}
]
}
to_ecto_schema( %Alembic.Document{ data: [Alembic.Resource.t()] | Alembic.Resource.t(), errors: term(), included: term(), jsonapi: term(), links: term(), meta: term() }, Alembic.ToParams.resource_by_id_by_type(), Alembic.ToEctoSchema.ecto_schema_module_by_type() ) :: [struct()] | struct()
Call to_ecto_schema/2
instead to automatically generate attributes_by_id_by_type
to_pagination(t()) :: Alembic.Pagination.t() | nil
Extract paged pagination information.
To support pagination, a document at minimum must have a "record_count"
meta
entry.
iex> Alembic.Document.to_pagination(
...> %Alembic.Document{meta: %{ "record_count" => 10 }}
...> )
%Alembic.Pagination{ total_size: 10 }
Without it, the "record_count"
meta
entry, the pagination will be nil
iex> Alembic.Document.to_pagination(%Alembic.Document{})
nil
Single Page
When there is only one page, there will be a "first"
and "last"
link pointing to the same page, but no
“next” or “prev” links. The total_size
as given by the meta
"record_count"
will between 0
and
the page size because the last is not necessarily full.
iex> Alembic.Document.to_pagination(
...> %Alembic.Document{
...> links: %{
...> "first" => "https://example.com/api/v1/users?page%5Bnumber%5D=1&page%5Bsize%5D=10",
...> "last" => "https://example.com/api/v1/users?page%5Bnumber%5D=1&page%5Bsize%5D=10"
...> },
...> meta: %{ "record_count" => 5 }
...> }
...> )
%Alembic.Pagination{
first: %Alembic.Pagination.Page{
number: 1,
size: 10
},
last: %Alembic.Pagination.Page{
number: 1,
size: 10
},
total_size: 5
}
Multiple Pages
When there are multiple pages, every page will have a "first"
and "last"
link pointing to the respective,
different pages. The total_size
as given by the meta
"record_count"
will be between
(last.number - 1) * last.size
and last.number * last.size
because the last page is not necessarily full.
On the first page, the "next"
link will be set, but not the "prev"
link.
iex> Alembic.Document.to_pagination(
...> %Alembic.Document{
...> links: %{
...> "first" => "https://example.com/api/v1/users?page%5Bnumber%5D=1&page%5Bsize%5D=10",
...> "last" => "https://example.com/api/v1/users?page%5Bnumber%5D=3&page%5Bsize%5D=10",
...> "next" => "https://example.com/api/v1/users?page%5Bnumber%5D=2&page%5Bsize%5D=10"
...> },
...> meta: %{ "record_count" => 25 }
...> }
...> )
%Alembic.Pagination{
first: %Alembic.Pagination.Page{
number: 1,
size: 10
},
last: %Alembic.Pagination.Page{
number: 3,
size: 10
},
next: %Alembic.Pagination.Page{
number: 2,
size: 10
},
total_size: 25
}
On any middle page, both the "next"
and "prev"
links will be set.
iex> Alembic.Document.to_pagination(
...> %Alembic.Document{
...> links: %{
...> "first" => "https://example.com/api/v1/users?page%5Bnumber%5D=1&page%5Bsize%5D=10",
...> "last" => "https://example.com/api/v1/users?page%5Bnumber%5D=3&page%5Bsize%5D=10",
...> "next" => "https://example.com/api/v1/users?page%5Bnumber%5D=3&page%5Bsize%5D=10",
...> "prev" => "https://example.com/api/v1/users?page%5Bnumber%5D=1&page%5Bsize%5D=10"
...> },
...> meta: %{ "record_count" => 25 }
...> }
...> )
%Alembic.Pagination{
first: %Alembic.Pagination.Page{
number: 1,
size: 10
},
last: %Alembic.Pagination.Page{
number: 3,
size: 10
},
next: %Alembic.Pagination.Page{
number: 3,
size: 10
},
previous: %Alembic.Pagination.Page{
number: 1,
size: 10
},
total_size: 25
}
On the last page, the "prev"
link will be set, but not the "next"
link.
iex> Alembic.Document.to_pagination(
...> %Alembic.Document{
...> links: %{
...> "first" => "https://example.com/api/v1/users?page%5Bnumber%5D=1&page%5Bsize%5D=10",
...> "last" => "https://example.com/api/v1/users?page%5Bnumber%5D=3&page%5Bsize%5D=10",
...> "prev" => "https://example.com/api/v1/users?page%5Bnumber%5D=2&page%5Bsize%5D=10"
...> },
...> meta: %{ "record_count" => 25 }
...> }
...> )
%Alembic.Pagination{
first: %Alembic.Pagination.Page{
number: 1,
size: 10
},
last: %Alembic.Pagination.Page{
number: 3,
size: 10
},
previous: %Alembic.Pagination.Page{
number: 2,
size: 10
},
total_size: 25
}
Transforms a t
into the nested params format used by
Ecto.Changeset.cast/4
.
No resource
No resource is transformed to an empty map
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => nil
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_params(document)
%{}
Single resource
A single resource is converted to a params map that combines the id and attributes.
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "attributes" => %{
...> "name" => "Thing 1"
...> },
...> "id" => "1",
...> "type" => "thing"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_params(document)
%{
"id" => "1",
"name" => "Thing 1"
}
Relationships
Relationships are merged into the params for the resource
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => %{
...> "attributes" => %{
...> "name" => "Thing 1"
...> },
...> "id" => "1",
...> "relationships" => %{
...> "shirt" => %{
...> "data" => %{
...> "attributes" => %{
...> "size" => "L"
...> },
...> "type" => "shirt"
...> }
...> }
...> },
...> "type" => "thing"
...> }
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_params(document)
%{
"id" => "1",
"name" => "Thing 1",
"shirt" => %{
"size" => "L"
}
}
Multiple resources
Multiple resources are converted to a params list where each element is a map that combines the id and attributes
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "type" => "post",
...> "id" => "1",
...> "attributes" => %{
...> "text" => "Welcome"
...> }
...> },
...> %{
...> "type" => "post",
...> "id" => "2",
...> "attributes" => %{
...> "text" => "It's been awhile"
...> }
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_params(document)
[
%{
"id" => "1",
"text" => "Welcome"
},
%{
"id" => "2",
"text" => "It's been awhile"
}
]
Relationships
Relationships are merged into the params for the corresponding resource
iex> {:ok, document} = Alembic.Document.from_json(
...> %{
...> "data" => [
...> %{
...> "type" => "post",
...> "id" => "1",
...> "attributes" => %{
...> "text" => "Welcome"
...> },
...> "relationships" => %{
...> "comments" => %{
...> "data" => [
...> %{
...> "type" => "comment",
...> "id" => "1"
...> }
...> ]
...> }
...> }
...> },
...> %{
...> "type" => "post",
...> "id" => "2",
...> "attributes" => %{
...> "text" => "It's been awhile"
...> },
...> "relationships" => %{
...> "comments" => %{
...> "data" => []
...> }
...> }
...> }
...> ],
...> "included" => [
...> %{
...> "type" => "comment",
...> "id" => "1",
...> "attributes" => %{
...> "text" => "First!"
...> }
...> }
...> ]
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :fetch,
...> "sender" => :server
...> },
...> source: %Alembic.Source{
...> pointer: ""
...> }
...> }
...> )
iex> Alembic.Document.to_params(document)
[
%{
"id" => "1",
"text" => "Welcome",
"comments" => [
%{
"id" => "1",
"text" => "First!"
}
]
},
%{
"id" => "2",
"text" => "It's been awhile",
"comments" => []
}
]
to_params( %Alembic.Document{ data: [Alembic.Resource.t()] | Alembic.Resource.t() | nil, errors: term(), included: term(), jsonapi: term(), links: term(), meta: term() }, Alembic.ToParams.resource_by_id_by_type() ) :: Alembic.ToParams.params()
Transforms a t
into the nested params format used by
Ecto.Changeset.cast/4
using the given
resources_by_id_by_type
.
to_params( %Alembic.Document{ data: [Alembic.Resource.t()] | Alembic.Resource.t() | nil, errors: term(), included: term(), jsonapi: term(), links: term(), meta: term() }, Alembic.ToParams.resource_by_id_by_type(), Alembic.ToParams.converted_by_id_by_type() ) :: Alembic.ToParams.params()
Transforms a t
into the nested params format used by
Ecto.Changeset.cast/4
using the given
resources_by_id_by_type
and converted_by_id_by_type
.
See InterpreterServer.Api.Document.to_params/1