Alembic v4.0.0 Alembic.Relationships View Source

The value of the relationships key MUST be an object (a “relationships object”). Members of the relationships object (“relationships”) represent references from the resource object in which it’s defined to other resource objects.

Relationships may be to-one or to-many.

JSON API - Document Structure - Resource Objects - Relationships

Link to this section Summary

Types

t()

Maps String.t name to relationship

Functions

Validates that the given json follows the spec for “relationship”

Converts relationships to params format used by Ecto.Changeset.cast/4 that can be merged with the params from the primary data

Unlike to_params/2, if type and id of convertable already exists in converted_by_id_by_type, then the params returned are only %{ "id" => id } without any further expansion, that is, a resource identifier, so that loops are prevented

Link to this section Types

Link to this type relationship() View Source
relationship() :: Alembic.json_object()

Maps String.t name to relationship

Link to this section Functions

Link to this function from_json(json, error_template) View Source
from_json(Alembic.json_object(), Alembic.Error.t()) ::
  {:ok, map()} | Alembic.FromJson.error()
from_json(nil, Alembic.Error.t()) :: {:ok, nil}
from_json(
  true | false | list() | float() | integer() | String.t(),
  Alembic.Error.t()
) :: Alembic.FromJson.error()

Validates that the given json follows the spec for “relationship”.

"relationships" is optional, so it can be nil.

iex> Alembic.Relationships.from_json(
...>   nil,
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships"
...>     }
...>   }
...> )
{:ok, nil}

Relationships

Members of the relationships object (“relationships”) represent references from the resource object in which it’s defined to other resource objects.

JSON API - Document Structure - Resource Objects - Relationships

iex> Alembic.Relationships.from_json(
...>   %{"shirt" => []},
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/relationships/shirt` type is not relationship",
        meta: %{
          "type" => "relationship"
        },
        source: %Alembic.Source{
          pointer: "/data/relationships/shirt"
        },
        status: "422",
        title: "Type is wrong"
      }
    ]
  }
}

Data

Resource linkage in a compound document allows a client to link together all of the included resource objects without having to GET any URLs via links.

Resource linkage MUST be represented as one of the following:

  • null for empty to-one relationships.
  • an empty array ([]) for empty to-many relationships.
  • a single resource identifier object for non-empty to-one relationships.
  • an array of resource identifier objects for non-empty to-many relationships.

JSON API - Document Structure - Resource Objects - Resource Linkage

To-one

A to-one relationship, when present, will be a single Alembic.ResourceIdentifier.t

iex> Alembic.Relationships.from_json(
...>   %{
...>     "author" => %{
...>       "data" => %{
...>         "id" => "1",
...>         "type" => "author"
...>       }
...>     }
...>   },
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships"
...>     }
...>   }
...> )
{
  :ok,
  %{
    "author" => %Alembic.Relationship{
      data: %Alembic.ResourceIdentifier{
        id: "1",
        type: "author"
      }
    }
  }
}

An empty to-one relationship can be signified with nil, which would have been null in the original JSON.

iex> Alembic.Relationships.from_json(
...>   %{
...>     "author" => %{
...>       "data" => nil
...>     }
...>   },
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships"
...>     }
...>   }
...> )
{
  :ok,
  %{
    "author" => %Alembic.Relationship{
      data: nil
    }
  }
}

To-many

A to-many relationship, when preent, will be a list of Alembic.ResourceIdentifier.t

iex> Alembic.Relationships.from_json(
...>   %{
...>     "comments" => %{
...>       "data" => [
...>         %{
...>           "id" => "1",
...>           "type" => "comment"
...>         }
...>       ]
...>     }
...>   },
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships"
...>     }
...>   }
...> )
{
  :ok,
  %{
    "comments" => %Alembic.Relationship{
      data: [
        %Alembic.ResourceIdentifier{
          id: "1",
          type: "comment"
        }
      ]
    }
  }
}

An empty to-many resource linkage can be signified with [].

iex> Alembic.Relationships.from_json(
...>   %{
...>     "comments" => %{
...>       "data" => []
...>     }
...>   },
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships"
...>     }
...>   }
...> )
{
  :ok,
  %{
    "comments" => %Alembic.Relationship{
      data: []
    }
  }
}
Link to this function to_params(relationship_by_name, resource_by_id_by_type) View Source

Converts relationships to params format used by Ecto.Changeset.cast/4 that can be merged with the params from the primary data.

No relationships

No relationships are represented as nil in Alembic.Document.t, but since the output of to_params/2 is expected to be Map.merge/2 with the primary resource’s params, an empty map is returned when relationships is nil.

iex> Alembic.Relationships.to_params(nil, %{})
%{}

Some Relationships

Relatonship params are expected to be Map.merge/2 with the primary resource’s params, so a relationships are returned as a map using the original relationship name and each Alembic.Relationship.t converted with Alembic.Relationship.to_params/2.

Resource Identifiers

If the resource linkage for a relationship is an Alembic.ResourceIdentifier.t, then the attributes for the resource will be looked up in resource_by_id_by_type.

iex> Alembic.Relationships.to_params(
...>   %{
...>     "author" => %Alembic.Relationship{
...>       data: %Alembic.ResourceIdentifier{id: "1", type: "author"}
...>     }
...>   },
...>   %{
...>     "author" => %{
...>       "1" => %Alembic.Resource{
...>         type: "author",
...>         id: "1",
...>         attributes: %{
...>           "name" => "Alice"
...>         }
...>       }
...>     }
...>   }
...> )
%{
  "author" => %{
    "id" => "1",
    "name" => "Alice"
  }
}

Resources are not required to be in resources_by_id_by_type, as would be the case when only a foreign key is supplied.

iex> Alembic.Relationships.to_params(
...>   %{
...>     "author" => %Alembic.Relationship{
...>       data: %Alembic.ResourceIdentifier{id: "1", type: "author"}
...>     }
...>   },
...>   %{}
...> )
%{
  "author" => %{
    "id" => "1"
  }
}

Resources

On create or update, the relationships can directly contain Alembic.Resource.t that are to be created, in which case the resource_by_id_by_type are ignored.

iex> Alembic.Relationships.to_params(
...>   %{
...>     "author" => %Alembic.Relationship{
...>       data: %Alembic.Resource{
...>         attributes: %{"name" => "Alice"},
...>         type: "author"
...>       }
...>     }
...>   },
...>   %{}
...> )
%{
  "author" => %{
    "name" => "Alice"
  }
}

Not Included

If a relationship is not included, then no linkage data may be present and {:error, :unset} will be returned from Alembic.Relationshio.to_params/3. For those relationships, they will not appear in the params of all relationships.

iex> Alembic.Relationships.to_params(
...>   %{
...>     "author" => %Alembic.Relationship{
...>       data: %Alembic.Resource{
...>         attributes: %{"name" => "Alice"},
...>         type: "author"
...>       }
...>     },
...>     "comments" => %Alembic.Relationship{
...>       links: %{
...>         "related" => "https://example.com/api/v1/posts/1/comments"
...>       }
...>     }
...>   },
...>   %{}
...> )
%{
  "author" => %{
    "name" => "Alice"
  }
}

Unlike to_params/2, if type and id of convertable already exists in converted_by_id_by_type, then the params returned are only %{ "id" => id } without any further expansion, that is, a resource identifier, so that loops are prevented.

Parameters

  • convertable - an Alembic.Document.t hierarchy data structure
  • resources_by_id_by_type - A nest map 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 from Alembic.Document.t included.
  • converted_by_id_by_type - Tracks which (type, id) have been converted already to prevent infinite recursion when expanding indirect relationships.

Returns

Success

  • {nil} if an empty singleton
  • %{} - if a non-empty singleton
  • [] - if an empty collection
  • [%{}] - if a non-empty collection

Errors

  • {:error, :already_converted} - if the type and id of convertable already exists in converted_by_id_by_type
  • {:error, :unset} - if the convertable data is not set

Callback implementation for Alembic.ToParams.to_params/3.