Subscribe to access all episodes. View plans →
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_schema
s. 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.
Jonathan Medding
6 months agoPretty slick.
Is it possible to do the same with migrations? I want to be able to see the current state of the DB based on all the applied migrations (without having to piece together a half-dozen migration files).