Alembic v4.0.0 Alembic.Fetch.Includes View Source

Fetching Data > Inclusion of Related Resources

Link to this section 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

Link to this section Types

A nested map of relationship_names

Link to this type params() View Source
params() :: %{}
Link to this type preload() View Source
preload() :: term()
Link to this type preload_by_include() View Source
preload_by_include() :: %{optional(include()) => preload()}

A list of includes

Link to this section Functions

Link to this function from_params(params) View Source
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"
    }
  }
]
Link to this function from_string(comma_separated_relationship_paths) View Source
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"
    }
  }
]
Link to this function relationship_path_separator() View Source

Separates each relationship path in includes

Link to this function to_preload(include, preload_by_include) View Source
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"
      }
    ]
  }
}
Link to this function to_preloads(includes, preload_by_include) View Source
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"
      }
    ]
  }
}
Link to this function to_query(includes, preload_by_include, query) View Source
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 t in Alembic.TestPost>
iex> {:ok, query_with_includes} = Alembic.Fetch.Includes.to_query(
...>   [],
...>   %{},
...>   query
...> )
...> query
#Ecto.Query<from t in 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 t in Alembic.TestPost>
iex> {:ok, query} = Alembic.Fetch.Includes.to_query(
...>   ["comments"],
...>   %{
...>     "comments" => :comments
...>   },
...>   query
...> )
...> query
#Ecto.Query<from t in Alembic.TestPost, preload: [[: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 t in 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"
      }
    ]
  }
}
Link to this function to_relationship_path(include) View Source
to_relationship_path(include()) :: Alembic.RelationshipPath.t()

Converts an include back to relationship path

Link to this function to_string(includes) View Source
to_string(includes :: [include()]) :: String.t()

Converts a list of include back to a string