Alembic v2.1.0 Alembic.Resource
The individual JSON object of elements of the list of the data
member of the
JSON API document are
resources as are the members of the included
member.
Summary
Types
The ID of a Resource.t
. Usually the primary key or UUID for a resource in the server
Resource objects” appear in a JSON API document to represent resources
The type of a Resource.t
. Can be either singular or pluralized, althought the JSON API spec examples favor
pluralized
Functions
Converts a JSON object into a JSON API Resource, t
Converts t
to Ecto.Schema.t
struct
Converts resource
to params format used by
Ecto.Changeset.cast/4
.
The id
and attributes
are combined into a single map for params
Callback implementation for Alembic.ToParams.to_params/3
Types
id :: String.t
The ID of a Resource.t
. Usually the primary key or UUID for a resource in the server.
t :: %Alembic.Resource{attributes: Alembic.json_object | nil, id: id | nil, links: Alembic.Links.t | nil, meta: Alembic.Meta.t | nil, relationships: Alembic.Relationships.t | nil, type: type}
Resource objects” appear in a JSON API document to represent resources.
A resource object MUST contain at least the following top-level members:
id
type
Exception: The id
member is not required when the resource object originates at the client and represents a new
resource to be created on the server. (%{action: :create, source: :client}
)
In addition, a resource object **MAY(( contain any of these top-level members:
attributes
- an attributes object representing some of the resource’s data.links
- anAlembic.Link.links
containing links related to the resource.meta
- contains non-standard meta-information about a resource that can not be represented as an attribute or relationship.relationships
- a relationships object describing relationships between the resource and other JSON API resources.
Functions
Specs
from_json(Alembic.json_object, %Alembic.Error{code: term, detail: term, id: term, links: term, meta: Alembic.Meta.t, source: term, status: term, title: term}) ::
{:ok, t} |
Alembic.FromJson.error
from_json(nil | true | false | list | float | integer | String.t, Alembic.Error.t) :: Alembic.FromJson.error
Converts a JSON object into a JSON API Resource, t
.
Invalid
A non-resource will be matched, but return an error.
iex> Alembic.Resource.from_json(
...> "1",
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data` type is not resource",
meta: %{
"type" => "resource"
},
source: %Alembic.Source{
pointer: "/data"
},
status: "422",
title: "Type is wrong"
}
]
}
}
Action
The Alembic.Error.t
meta
"action"
key influences whether "id"
is required: "id"
is optional
for "action"
:create
sent from "sender"
:client
; otherwise, it "id"
is required.
Creating
Only "type"
is required when creating a resource from a client because the "id"
will be assigned by the server.
iex> Alembic.Resource.from_json(
...> %{ "type" => "thing" },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{:ok, %Alembic.Resource{type: "thing"}}
Only "type"
will be marked as missing when creating a resource from a client
iex> Alembic.Resource.from_json(
...> %{},
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/type` is missing",
meta: %{
"child" => "type"
},
source: %Alembic.Source{
pointer: "/data"
},
status: "422",
title: "Child missing"
}
]
}
}
But, normally you’d include some "attributes"
too
iex> Alembic.Resource.from_json(
...> %{
...> "attributes" => %{
...> "name" => "Thing 1"
...> },
...> "type" => "thing"
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:ok,
%Alembic.Resource{
attributes: %{
"name" => "Thing 1"
},
type: "thing"
}
}
"attributes"
are quite free-form, but must still be an Alembic.json_object
iex> Alembic.Resource.from_json(
...> %{
...> "attributes" => [
...> "name"
...> ],
...> "type" => "thing"
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/attributes` type is not json object",
meta: %{
"type" => "json object"
},
source: %Alembic.Source{
pointer: "/data/attributes"
},
status: "422",
title: "Type is wrong"
}
]
}
}
Deleting
Only "id"
and "type"
is required when when deleting a resource from a client
iex> Alembic.Resource.from_json(
...> %{ "id" => "1", "type" => "thing"},
...> %Alembic.Error{
...> meta: %{
...> "action" => :delete,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{:ok, %Alembic.Resource{id: "1", type: "thing"}}
With "id"
, "type"
will be marked as missing when deleting a resource from a client
iex> Alembic.Resource.from_json(
...> %{ "id" => "1" },
...> %Alembic.Error{
...> meta: %{
...> "action" => :delete,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/type` is missing",
meta: %{
"child" => "type"
},
source: %Alembic.Source{
pointer: "/data"
},
status: "422",
title: "Child missing"
}
]
}
}
With "type"
, "id"
will be marked as missing when deleting a resource from a client
iex> Alembic.Resource.from_json(
...> %{ "type" => "thing" },
...> %Alembic.Error{
...> meta: %{
...> "action" => :delete,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/id` is missing",
meta: %{
"child" => "id"
},
source: %Alembic.Source{
pointer: "/data"
},
status: "422",
title: "Child missing"
}
]
}
}
Both "id"
and "type"
will be marked as missing when deleting a resource from a client
iex> Alembic.Resource.from_json(
...> %{},
...> %Alembic.Error{
...> meta: %{
...> "action" => :delete,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/id` is missing",
meta: %{
"child" => "id"
},
source: %Alembic.Source{
pointer: "/data"
},
status: "422",
title: "Child missing"
},
%Alembic.Error{
detail: "`/data/type` is missing",
meta: %{
"child" => "type"
},
source: %Alembic.Source{
pointer: "/data"
},
status: "422",
title: "Child missing"
}
]
}
}
Updating
Only "id"
and "type"
is required when when updating a resource from a client
iex> Alembic.Resource.from_json(
...> %{ "id" => "1", "type" => "thing"},
...> %Alembic.Error{
...> meta: %{
...> "action" => :update,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{:ok, %Alembic.Resource{id: "1", type: "thing"}}
With "id"
, "type"
will be marked as missing when upating a resource from a client
iex> Alembic.Resource.from_json(
...> %{ "id" => "1" },
...> %Alembic.Error{
...> meta: %{
...> "action" => :update,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/type` is missing",
meta: %{
"child" => "type"
},
source: %Alembic.Source{
pointer: "/data"
},
status: "422",
title: "Child missing"
}
]
}
}
With "type"
, "id"
will be marked as missing when updating a resource from a client
iex> Alembic.Resource.from_json(
...> %{ "type" => "thing" },
...> %Alembic.Error{
...> meta: %{
...> "action" => :update,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/id` is missing",
meta: %{"child" => "id"},
source: %Alembic.Source{
pointer: "/data"
},
status: "422",
title: "Child missing"
}
]
}
}
Both "id"
and "type"
will be marked as missing when updating a resource from a client
iex> Alembic.Resource.from_json(
...> %{},
...> %Alembic.Error{
...> meta: %{
...> "action" => :update,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/id` is missing",
meta: %{"child" => "id"},
source: %Alembic.Source{
pointer: "/data"
},
status: "422",
title: "Child missing"
},
%Alembic.Error{
detail: "`/data/type` is missing",
meta: %{
"child" => "type"
},
source: %Alembic.Source{
pointer: "/data"
},
status: "422",
title: "Child missing"
}
]
}
}
Optional members
"links"
, "meta"
and "relationships"
are optional, but if they are present, they MUST be valid or their
errors will make the overall t
invalid.
"links"
A valid "links"
maps link names to either a JSON object with "href"
and/or "meta"
member or a String.t
URL.
iex> Alembic.Resource.from_json(
...> %{
...> "links" => %{
...> "string" => "http://example.com",
...> "link_object" => %{
...> "href" => "http://example.com",
...> "meta" => %{
...> "last_updated_on" => "2015-12-21"
...> }
...> }
...> },
...> "type" => "thing"
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:ok,
%Alembic.Resource{
links: %{
"link_object" => %Alembic.Link{
href: "http://example.com",
meta: %{
"last_updated_on" => "2015-12-21"
}
},
"string" => "http://example.com"
},
type: "thing"
}
}
Even though "links"
is optional, if it has errors, those errors will make the entire resource invalid
iex> Alembic.Resource.from_json(
...> %{
...> "links" => ["http://example.com"],
...> "type" => "thing"
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/links` type is not links object",
meta: %{
"type" => "links object"
},
source: %Alembic.Source{
pointer: "/data/links"
},
status: "422",
title: "Type is wrong"
}
]
}
}
"meta"
A valid "meta"
is any JSON object
iex> Alembic.Resource.from_json(
...> %{
...> "meta" => %{"copyright" => "© 2015"},
...> "type" => "thing"
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:ok,
%Alembic.Resource{
meta: %{
"copyright" => "© 2015"
},
type: "thing"
}
}
If "meta"
isn’t a JSON object, then that error will make the whole resource invalid
iex> Alembic.Resource.from_json(
...> %{
...> "meta" => "© 2015",
...> "type" => "thing"
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/meta` type is not meta object",
meta: %{
"type" => "meta object"
},
source: %Alembic.Source{
pointer: "/data/meta"
},
status: "422",
title: "Type is wrong"
}
]
}
}
Relationships
"relationships"
allow linking to other resource in the documents "included"
using resource identifiers
iex> Alembic.Resource.from_json(
...> %{
...> "relationships" => %{
...> "shirt" => %{
...> "data" => %{
...> "id" => "1",
...> "type" => "shirt"
...> }
...> }
...> },
...> "type" => "thing"
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:ok,
%Alembic.Resource{
relationships: %{
"shirt" => %Alembic.Relationship{
data: %Alembic.ResourceIdentifier{
id: "1",
type: "shirt"
}
}
},
type: "thing"
}
}
If any relationship has an error, then it will make the entire resource invalid
iex> Alembic.Resource.from_json(
...> %{
...> "relationships" => %{
...> "shirt" => %{
...> "data" => %{}
...> }
...> },
...> "type" => "thing"
...> },
...> %Alembic.Error{
...> meta: %{
...> "action" => :create,
...> "sender" => :client
...> },
...> source: %Alembic.Source{
...> pointer: "/data"
...> }
...> }
...> )
{
:error,
%Alembic.Document{
errors: [
%Alembic.Error{
detail: "`/data/relationships/shirt/data/type` is missing",
meta: %{
"child" => "type"
},
source: %Alembic.Source{
pointer: "/data/relationships/shirt/data"
},
status: "422",
title: "Child missing"
},
%Alembic.Error{
detail: "`/data/relationships/shirt/data/id` is missing",
meta: %{
"child" => "id"
},
source: %Alembic.Source{
pointer: "/data/relationships/shirt/data"
},
status: "422",
title: "Child missing"
}
]
}
}
Specs
to_ecto_schema(t, Alembic.ToParams.resource_by_id_by_type, Alembic.ToEctoSchema.ecto_schema_module_by_type) :: struct
Converts t
to Ecto.Schema.t
struct.
The id
and attributes
are combined into the struct.
iex> Alembic.Resource.to_ecto_schema(
...> %Alembic.Resource{
...> attributes: %{
...> "text" => "First!"
...> },
...> id: "1",
...> type: "post"
...> },
...> %{},
...> %{
...> "post" => Alembic.TestPost
...> }
...> )
%Alembic.TestPost{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "posts"},
state: :built
},
id: 1,
text: "First!"
}
id
as nil
will pass through to the struct.
iex> Alembic.Resource.to_ecto_schema(
...> %Alembic.Resource{
...> attributes: %{
...> "text" => "First!"
...> },
...> type: "post"
...> },
...> %{},
...> %{
...> "post" => Alembic.TestPost
...> }
...> )
%Alembic.TestPost{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "posts"},
state: :built
},
text: "First!"
}
Relationships
Relationships’s are merged into the resource
’s struct using the relationship name (converted to an atom) as the key
in the resource
struct.
iex> Alembic.Resource.to_ecto_schema(
...> %Alembic.Resource{
...> attributes: %{"text" => "First!"},
...> relationships: %{
...> "author" => %Alembic.Relationship{
...> data: %Alembic.ResourceIdentifier{id: 1, type: "author"}
...> }
...> },
...> type: "post"
...> },
...> %{},
...> %{
...> "author" => Alembic.TestAuthor,
...> "post" => Alembic.TestPost
...> }
...> )
%Alembic.TestPost{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "posts"},
state: :built
},
author: %Alembic.TestAuthor{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "authors"},
state: :built
},
id: 1
},
author_id: 1,
text: "First!"
}
If the relationships does not have have an id (as may be the case where the server does not include linkage data
always), then the association will be %Ecto.Association.NotLoaded{}
and its foriegn key will be nil
.
iex> Alembic.Resource.to_ecto_schema(
...> %Alembic.Resource{
...> attributes: %{"text" => "First!"},
...> relationships: %{
...> "author" => %Alembic.Relationship{
...> links: %{
...> "related" => "https://example.com/api/v1/posts/1/author"
...> }
...> }
...> },
...> type: "post"
...> },
...> %{},
...> %{
...> "author" => Alembic.TestAuthor,
...> "post" => Alembic.TestPost
...> }
...> )
%Alembic.TestPost{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "posts"},
state: :built
},
author: %Ecto.Association.NotLoaded{
__cardinality__: :one,
__field__: :author,
__owner__: Alembic.TestPost
},
author_id: nil,
text: "First!"
}
If a relationship does not exist on the Ecto.Schema struct as an association, then it is ignored, so that incomplete models can be used and the sending and receiving-side don’t have to remain in strict sync.
iex> Alembic.Resource.to_ecto_schema(
...> %Alembic.Resource{
...> attributes: %{"text" => "First!"},
...> relationships: %{
...> "author" => %Alembic.Relationship{
...> data: %Alembic.ResourceIdentifier{id: 1, type: "author"}
...> },
...> "editor" => %Alembic.Relationship{
...> links: %{
...> "related" => "https://example.com/api/v1/posts/1/editor"
...> }
...> }
...> },
...> type: "post"
...> },
...> %{},
...> %{
...> "author" => Alembic.TestAuthor,
...> "post" => Alembic.TestPost
...> }
...> )
%Alembic.TestPost{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "posts"},
state: :built
},
author: %Alembic.TestAuthor{
__meta__: %Ecto.Schema.Metadata{
source: {nil, "authors"},
state: :built
},
id: 1
},
author_id: 1,
text: "First!"
}
Specs
to_params(t, Alembic.ToParams.resource_by_id_by_type) :: Alembic.ToParams.params
Converts resource
to params format used by
Ecto.Changeset.cast/4
.
The id
and attributes
are combined into a single map for params.
iex> Alembic.Resource.to_params(
...> %Alembic.Resource{
...> attributes: %{"text" => "First!"},
...> id: "1",
...> type: "post"
...> },
...> %{}
...> )
%{
"id" => "1",
"text" => "First!"
}
But, id
won’t show up as “id” in params if it is nil
iex> Alembic.Resource.to_params(
...> %Alembic.Resource{
...> attributes: %{"text" => "First!"},
...> type: "post"
...> },
...> %{}
...> )
%{
"text" => "First!"
}
Relationships
Relationships’s params are merged into the resource
’s params
iex> Alembic.Resource.to_params(
...> %Alembic.Resource{
...> attributes: %{"text" => "First!"},
...> relationships: %{
...> "author" => %Alembic.Relationship{
...> data: %Alembic.ResourceIdentifier{id: 1, type: "author"}
...> }
...> },
...> type: "post"
...> },
...> %{}
...> )
%{
"text" => "First!",
"author" => %{
"id" => 1
}
}
Specs
Callback implementation for Alembic.ToParams.to_params/3
.