Subscribe to access all episodes. View plans →

#121: Introspecting Ecto Schemas

Published August 11, 2020

Elixir 1.10.3

Ecto 3.4

Ecto.Schema module reflection docs


I recently needed to dynamically get data about an Ecto Schema. After searching the Ecto docs I discovered the __schema__ function. Let’s check it out.

We’ll start an IEx session with our project. This is a blog application that has a Post schema to handle our blog posts. Let’s alias it and do some introspection.

First let’s see what fields the schema has, which we can do by calling the __schema__ function passing in the :fields atom. We see all of the fields for our Post schema. One thing to remember is that this only returns non-virtual fields, so if you have a virtual field in your schema, it wont be returned here.

$ iex -S mix
Interactive Elixir (1.10.3)
> alias Teacher.Blog.Post
Teacher.Blog.Post
> fields = Post.__schema__(:fields)
[:id, :body, :published, :time_to_read, :title, :post_id, :inserted_at,
 :updated_at]

We have our post fields here, which is great, but how do we take it a step further and see what the type is for any field. Luckily we can call Post.__schema__ passing in the :type atom and the field we want to return the type for. If we try this for the post’s body we see that it’s a string.

Let’s see what the type is for each field. We’ll take our fields, and pipe them into Enum.map, which returns our field, and then the type.

> fields |> Enum.map(fn(field) -> {field, Post.__schema__(:type, field)} end)
[
  id: :id,
  body: :string,
  published: :boolean,
  time_to_read: :integer,
  title: :string,
  post_id: :id,
  inserted_at: :naive_datetime,
  updated_at: :naive_datetime
]

If we open the Post module. We see all our fields that were displayed are here in our schema. But, there is one thing missing. Our Post has_many :comments.

Let’s go back to our console and in order to get any associations a schema may have, we can use the __schema__ function again, passing in the :associations atom. Just like we’d expect our comments were returned as an association for our Post. We can get info about the relationship by calling Post.__schema__ passing in the atom :association and then name of the association we want data about. An %Ecto.Association.Has{} struct is returned that includes details about the association.

Let’s also try this for the Comment schema. This time an %Ecto.Association.BelongsTo{} struct is returned.

> Post.__schema__(:associations)
[:comments]
> Post.__schema__(:association, :comments)
%Ecto.Association.Has{
  cardinality: :many,
  defaults: [],
  field: :comments,
  on_cast: nil,
  on_delete: :nothing,
  on_replace: :raise,
  ordered: false,
  owner: Teacher.Blog.Post,
  owner_key: :id,
  queryable: Teacher.Blog.Comment,
  related: Teacher.Blog.Comment,
  related_key: :post_id,
  relationship: :child,
  unique: true,
  where: []
}
> Teacher.Blog.Comment.__schema__(:association, :post)
%Ecto.Association.BelongsTo{
  cardinality: :one,
  defaults: [],
  field: :post,
  on_cast: nil,
  on_replace: :raise,
  ordered: false,
  owner: Teacher.Blog.Comment,
  owner_key: :post_id,
  queryable: Teacher.Blog.Post,
  related: Teacher.Blog.Post,
  related_key: :id,
  relationship: :parent,
  unique: true,
  where: []
}

This also works for embedded_schemas. We have Registration module that uses an embedded_schema to help with user sign up. Let’s alias it and get the fields for our embedded_schema

> alias Teacher.Accounts.Registration
Teacher.Accounts.Registration
> Registration.__schema__(:fields)
[:email, :name, :receives_newsletter, :password]

If we open that module, the fields we expect are all there.

Let’s look at one last way to use the __schema__ function. If we pass in the atom :query an Ecto.Query for our schema is returned. We could extend this or use as-is to get all the records for the given Ecto schema.

> qry = Post.__schema__(:query)
#Ecto.Query<from p0 in Teacher.Blog.Post>
> Teacher.Repo.all(qry)
...

Check out the Ecto.Schema docs, for a full list of the different ways you can use the __schema__ function.

© 2020 HEXMONSTER LLC