Alembic v2.1.0 Alembic.Fetch.Includes

Fetching Data > Inclusion of Related Resources

Summary

Types

A nested map of relationship_names

t()

A list of includes

Functions

Extract t from "include" in params

Breaks the relationship_path into relationship_names in a nested map to form multiple includes

Separates each relationship path in includes

Converts a String-based include that uses relationship names to Atom-based association names used in a preload

Converts the String-based includes that use relationship names to Atom-based association names used in preloads

Add preloads for includes to query

Converts an include back to relationship path

Converts a list of include back to a string

Types

params :: %{}
preload :: term
t :: [include]

A list of includes

Functions

from_params(params)

Specs

from_params(params) :: [include]

Extract t from "include" in params

params without "include" will have no includes

iex> Alembic.Fetch.Includes.from_params(%{})
[]

params with "include" will have the value of "includes" broken into t

iex> Alembic.Fetch.Includes.from_params(
...>   %{
...>     "include" => "author,comments.author.posts"
...>   }
...> )
[
  "author",
  %{
    "comments" => %{
      "author" => "posts"
    }
  }
]
from_string(comma_separated_relationship_paths)

Specs

from_string(String.t) :: t

Breaks the relationship_path into relationship_names in a nested map to form multiple includes

An empty String will have no includes

iex> Alembic.Fetch.Includes.from_string("")
[]

A single relationship name will become the only include

iex> Alembic.Fetch.Includes.from_string("comments")
["comments"]

A relationship path will become the only include

iex> Alembic.Fetch.Includes.from_string("comments.author.posts")
[
  %{
    "comments" => %{
      "author" => "posts"
    }
  }
]

Each relationship name or relationship path will be a separate element in includes

iex> Alembic.Fetch.Includes.from_string("author,comments.author.posts")
[
  "author",
  %{
    "comments" => %{
      "author" => "posts"
    }
  }
]
relationship_path_separator()

Separates each relationship path in includes

to_preload(include, preload_by_include)

Specs

to_preload(include, preload_by_include) ::
  {:ok, term} |
  {:error, Alembic.Document.t}

Converts a String-based include that uses relationship names to Atom-based association names used in a preload

Relationship Name

A relationship name is looked up in preload_by_include

iex> Alembic.Fetch.Includes.to_preload(
...>   "comments",
...>   %{
...>     "comments" => :comments
...>   }
...> )
{:ok, :comments}

A relationship name not found in preload_by_include will generate a JSON API errors Document with an error on the “include” parameter

iex> Alembic.Fetch.Includes.to_preload(
...>   "secret",
...>   %{}
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`secret` is an unknown relationship path",
        meta: %{
          "relationship_path" => "secret"
        },
        source: %Alembic.Source{
          parameter: "include"
        },
        title: "Unknown relationship path"
      }
    ]
  }
}

Relationship Path

A relationship path is looked up in preload_by_include directly, and NOT recursively, so the key itself needs to be an include

iex> Alembic.Fetch.Includes.to_preload(
...>   %{
...>     "comments" => "author"
...>   },
...>   %{
...>     %{
...>       "comments" => "author"
...>     } => [comments: :author]
...>   }
...> )
{:ok, [comments: :author]}

A relationship path not found in preload_by_include will generate a JSON API errors Document with an error on the “include” parameter

iex> Alembic.Fetch.Includes.to_preload(
...>   %{
...>     "secret" => "super-secret"
...>   },
...>   %{}
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`secret.super-secret` is an unknown relationship path",
        meta: %{
          "relationship_path" => "secret.super-secret"
        },
        source: %Alembic.Source{
          parameter: "include"
        },
        title: "Unknown relationship path"
      }
    ]
  }
}
to_preloads(includes, preload_by_include)

Specs

to_preloads(t, preload_by_include) ::
  {:ok, list} |
  {:error, Alembic.Document.t}

Converts the String-based includes that use relationship names to Atom-based association names used in preloads

With no includes, there will be no preloads, and so the conversion map doesn’t matter

iex> Alembic.Fetch.Includes.to_preloads([], %{})
{:ok, []}

Relationship names

Relationship names are looked up in preload_by_include

iex> Alembic.Fetch.Includes.to_preloads(
...>   ~w{comments links},
...>   %{
...>     "comments" => :comments,
...>      "links" => :links
...>   }
...> )
{
  :ok,
  [
    :comments,
    :links
  ]
}

Relationship names that are not found in preload_by_include will return a merged JSON API errors document. The error document will hide any includes that could be found.

iex> Alembic.Fetch.Includes.to_preloads(
...>   ~w{secret comments hidden links},
...>   %{
...>      "comments" => :comments,
...>      "links" => :links
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`secret` is an unknown relationship path",
        meta: %{
          "relationship_path" => "secret"
        },
        source: %Alembic.Source{
          parameter: "include"
        },
        title: "Unknown relationship path"
      },
      %Alembic.Error{
        detail: "`hidden` is an unknown relationship path",
        meta: %{
          "relationship_path" => "hidden"
        },
        source: %Alembic.Source{
          parameter: "include"
        },
        title: "Unknown relationship path"
      }
    ]
  }
}

Relationship Paths

Relationship paths are a looked up in preload_by_include directly, and NOT recursively, so the key itself needs to be an include

iex> Alembic.Fetch.Includes.to_preloads(
...>   [
...>     %{ "comments" => "author" },
...>     %{ "links" => "clickers" }
...>   ],
...>   %{
...>     %{ "comments" => "author" } => [comments: :author],
...>     %{ "links" => "clickers" } => [links: :clickers]
...>   }
...> )
{
  :ok,
  [
    [comments: :author],
    [links: :clickers]
  ]
}

Relationship paths that are not found in preload_by_include will return a merged JSON API errors document. The error document wil hide any includes that could be found.

iex> Alembic.Fetch.Includes.to_preloads(
...>   [
...>     %{ "comments" => "secret" },
...>     %{ "comments" => "author" },
...>     %{ "links" => "hidden" },
...>     %{ "links" => "clickers" }
...>   ],
...>   %{
...>     %{ "comments" => "author" } => [comments: :author],
...>     %{ "links" => "clickers" } => [links: :clickers]
...>   }
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`comments.secret` is an unknown relationship path",
        meta: %{
          "relationship_path" => "comments.secret"
        },
        source: %Alembic.Source{
          parameter: "include"
        },
        title: "Unknown relationship path"
      },
      %Alembic.Error{
        detail: "`links.hidden` is an unknown relationship path",
        meta: %{
          "relationship_path" => "links.hidden"
        },
        source: %Alembic.Source{
          parameter: "include"
        },
        title: "Unknown relationship path"
      }
    ]
  }
}
to_query(includes, preload_by_include, query)

Specs

to_query(t, preload_by_include, Ecto.Query.t) ::
  {:ok, Ecto.Query.t} |
  {:error, Alembic.Document.t}

Add preloads for includes to query.

If there are no includes, then the query is returns unchanged.

iex> require Ecto.Query
iex> query = Ecto.Query.from p in Alembic.TestPost
%Ecto.Query{
  from: {"posts", Alembic.TestPost}
}
iex> {:ok, query_with_includes} = Alembic.Fetch.Includes.to_query(
...>   [],
...>   %{},
...>   query
...> )
{
  :ok,
  %Ecto.Query{
    from: {"posts", Alembic.TestPost}
  }
}
iex> query_with_includes == query
true

If there are includes, they are converted to preloads and added to the query

iex> require Ecto.Query
iex> query = Ecto.Query.from p in Alembic.TestPost
%Ecto.Query{
  from: {"posts", Alembic.TestPost}
}
iex> Alembic.Fetch.Includes.to_query(
...>   ["comments"],
...>   %{
...>     "comments" => :comments
...>   },
...>   query
...> )
{
  :ok,
  %Ecto.Query{
    from: {"posts", Alembic.TestPost},
    preloads: [[:comments]]
  }
}

If the includes can’t be converted to preloads, then the conversion errors are returned

iex> require Ecto.Query
iex> query = Ecto.Query.from p in Alembic.TestPost
%Ecto.Query{
  from: {"posts", Alembic.TestPost}
}
iex> Alembic.Fetch.Includes.to_query(
...>   ["secret"],
...>   %{},
...>   query
...> )
{
  :error,
  %Alembic.Document{
    errors: [
      %Alembic.Error{
        detail: "`secret` is an unknown relationship path",
        meta: %{
          "relationship_path" => "secret"
        },
        source: %Alembic.Source{
          parameter: "include"
        },
        title: "Unknown relationship path"
      }
    ]
  }
}
to_relationship_path(include)

Specs

to_relationship_path(include) :: Alembic.RelationshipPath.t

Converts an include back to relationship path

to_string(includes)

Specs

to_string(includes :: [include]) :: String.t

Converts a list of include back to a string