Alembic v2.1.0 Alembic.Relationship

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

Summary

Types

t()

A “relationship object” MUST contain at least one of the following:

  • links - a links object containing at least one of the following:

    • self - a link for the relationship itself (a “relationship link”). This link allows the client to directly manipulate the relationship. For example, removing an author through an article’s relationship URL would disconnect the person from the article without deleting the people resource itself. When fetched successfully, this link returns the linkage for the related resources as its primary data. (See Fetching Relationships.)
    • related - a related resource link
  • data - resource linkage
  • meta - a meta object that contains non-standard meta-information about the relationship.

JSON API - Document Structure - Resource Objects - Relationships

Functions

Converts t to params format used by Ecto.Changeset.cast/4, but unlike to_params/2, will skip converting data in t where the type and id are already in converted_by_id_by_type

Types

t :: %Alembic.Relationship{data: [ResourceIdentifier.t] | ResourceIdentifier.t | nil | :unset, links: Alembic.Links.links, meta: Alembic.Meta.t}

A “relationship object” MUST contain at least one of the following:

  • links - a links object containing at least one of the following:

    • self - a link for the relationship itself (a “relationship link”). This link allows the client to directly manipulate the relationship. For example, removing an author through an article’s relationship URL would disconnect the person from the article without deleting the people resource itself. When fetched successfully, this link returns the linkage for the related resources as its primary data. (See Fetching Relationships.)
    • related - a related resource link
  • data - resource linkage
  • meta - a meta object that contains non-standard meta-information about the relationship.

JSON API - Document Structure - Resource Objects - Relationships

Functions

from_json(json, error_template)

Specs

from_json(Alembic.json_object, Alembic.Error.t) ::
  {:ok, t} |
  Alembic.FromJson.error
from_json(nil | true | false | list | float | integer | String.t, Alembic.Error.t) :: Alembic.FromJson.error

Relationships

A non-object will be matched, but return an error.

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

An object without any of the members will show them as all missing

iex> Alembic.Relationship.from_json(
...>   %{},
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships/author"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "At least one of the following children of `/data/relationships/author` must be present:\n" <>
                "data\n" <>
                "links\n" <>
                "meta",
        meta: %{
          "children" => [
            "data",
            "links",
            "meta"
          ]
        },
        source: %Alembic.Source{
          pointer: "/data/relationships/author"
        },
        status: "422",
        title: "Not enough children"
      }
    ]
  }
}

Resource Linkage

To-one

"data" can be a single resource identifier

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

An empty to-one relationship can be signified with nil for "data"

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

To-many

"data" can be a list of resource identifiers

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

An empty to-many relationship can be signified with []

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

Invalid

There are actual, invalid values for resource linkages, such as strings and numbers

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

Not Loaded

A relationship may have no data, neither nil nor an empty list, when the server only includes the linkage data when include=<relationship-name> is passed. In such as case, the Alembic.Relationship.t will have :unset for the data to differentiate it from nil, which represent an empty to-one relationship where "data": null was in the encoded JSON.

iex> Alembic.Relationship.from_json(
...>   %{"links" => %{"related" => "http://example.com/api/v1/posts/1/comments"}},
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships/comments"
...>     }
...>   }
...> )
{
  :ok,
  %Alembic.Relationship{
    data: :unset,
    links: %{
      "related" => "http://example.com/api/v1/posts/1/comments"
    }
  }
}

Links

"links" if present, must be an object

iex> Alembic.Relationship.from_json(
...>   %{"links" => ["http://example.com"]},
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships/website"
...>     }
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`/data/relationships/website/links` type is not links object",
        meta: %{
          "type" => "links object"
        },
        source: %Alembic.Source{
          pointer: "/data/relationships/website/links"
        },
        status: "422",
        title: "Type is wrong"
      }
    ]
  }
}

Because the “links” name are free-form, they remain strings

iex> Alembic.Relationship.from_json(
...>   %{"links" => %{"example" => "http://example.com"}},
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships/website"
...>     }
...>   }
...> )
{
  :ok,
  %Alembic.Relationship{
    links: %{
      "example" => "http://example.com"
    }
  }
}

“links” can be mix of String.t URLs and Alembic.Link.t objects

iex> Alembic.Relationship.from_json(
...>   %{
...>     "links" => %{
...>       "link_object" => %{
...>          "href" => "http://example.com/link_object/href"
...>       },
...>       "string" => "http://example.com/string"
...>     }
...>   },
...>   %Alembic.Error{
...>     source: %Alembic.Source{
...>       pointer: "/data/relationships/website"
...>     }
...>   }
...> )
{
  :ok,
  %Alembic.Relationship{
    links: %{
      "link_object" => %Alembic.Link{
        href: "http://example.com/link_object/href"
      },
      "string" => "http://example.com/string"
    }
  }
}
to_params(relationship, resource_by_id_by_type)

Specs

to_params(%Alembic.Relationship{data: any, links: term, meta: term}, Alembic.ToParams.resource_by_id_by_type) ::
  Alembic.ToParams.params |
  {:error, :unset}

Converts t to params format used by Ecto.Changeset.cast/4.

To-one

An empty to-one, nil, is nil when converted to params.

iex> Alembic.Relationship.to_params(%Alembic.Relationship{data: nil}, %{})
nil

A resource identifier uses resource_by_id_by_type to fill in the attributes of the referenced resource. type is dropped as Ecto.Changeset.cast/4 doesn’t verify types in the params.

iex> Alembic.Relationship.to_params(
...>   %Alembic.Relationship{
...>     data: %Alembic.ResourceIdentifier{
...>       id: "1", type: "shirt"
...>     }
...>   },
...>   %{
...>     "shirt" => %{
...>       "1" => %Alembic.Resource{
...>         type: "shirt",
...>         id: "1",
...>         attributes: %{
...>           "size" => "L"
...>         }
...>       }
...>     }
...>   }
...> )
%{
  "id" => "1",
  "size" => "L"
}

On create or update, a relationship can be created by having an Alembic.Resource.t, in which case the attributes are supplied by the Alembic.Resource.t, instead of resource_by_id_by_type.

iex> Alembic.Relationship.to_params(
...>   %Alembic.Relationship{
...>     data: %Alembic.Resource{
...>       attributes: %{
...>         "size" => "L"
...>       },
...>       type: "shirt"
...>     }
...>   },
...>   %{}
...> )
%{
  "size" => "L"
}

To-many

An empty to-many, [], is [] when converted to params

iex> Alembic.Relationship.to_params(%Alembic.Relationship{data: []}, %{})
[]

A list of resource identifiers uses resource_by_id_by_type to fill in the attributes of the referenced resources. type is dropped as Ecto.Changeset.cast/4 doesn’t verify types in the params.

iex> Alembic.Relationship.to_params(
...>   %Alembic.Relationship{
...>     data: [
...>       %Alembic.ResourceIdentifier{
...>         id: "1", type: "shirt"
...>       }
...>     ]
...>   },
...>   %{
...>     "shirt" => %{
...>       "1" => %Alembic.Resource{
...>         type: "shirt",
...>         id: "1",
...>         attributes: %{
...>           "size" => "L"
...>         }
...>       }
...>     }
...>  }
...> )
[
  %{
    "id" => "1",
    "size" => "L"
  }
]

On create or update, a relationship can be created by having an Alembic.Resource.t, in which case the attributes are supplied by the Alembic.Resource, instead of resource_by_id_by_type.

iex> Alembic.Relationship.to_params(
...>   %Alembic.Relationship{
...>     data: [
...>       %Alembic.Resource{
...>         attributes: %{
...>           "size" => "L"
...>         },
...>         type: "shirt"
...>       }
...>     ]
...>   },
...>   %{}
...> )
[
  %{
    "size" => "L"
  }
]

Not Included

If a relationship is not included, then no linkage data may be present and {:error, :unset} will be returned

iex> Alembic.Relationship.to_params(
...>   %Alembic.Relationship{
...>     data: :unset
...>   },
...>   %{}
...> )
{:error, :unset}
to_params(relationship, resource_by_id_by_type, converted_by_id_by_type)

Specs

to_params(%Alembic.Relationship{data: any, links: term, meta: term}, Alembic.ToParams.resource_by_id_by_type, Alembic.ToParams.converted_by_id_by_type) ::
  Alembic.ToParams.params |
  {:error, :already_converted | :unset}

Converts t to params format used by Ecto.Changeset.cast/4, but unlike to_params/2, will skip converting data in t where the type and id are already in converted_by_id_by_type.