Packages
ecto
0.6.0
3.14.1
3.14.0
3.13.6
3.13.5
3.13.4
3.13.3
3.13.2
3.13.1
3.13.0
3.12.6
3.12.5
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.2
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.4
3.8.3
3.8.2
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.2
3.6.1
3.6.0
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.5.0-rc.1
3.5.0-rc.0
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.4
3.3.3
3.3.2
3.3.1
3.3.0
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.1
3.0.0-rc.0
2.2.12
2.2.11
2.2.10
2.2.9
2.2.8
2.2.7
2.2.6
2.2.5
2.2.4
2.2.3
2.2.2
2.2.1
2.2.0
2.2.0-rc.1
2.2.0-rc.0
2.1.6
2.1.5
2.1.4
2.1.3
2.1.2
2.1.1
2.1.0
2.1.0-rc.5
2.1.0-rc.4
2.1.0-rc.3
2.1.0-rc.2
2.1.0-rc.1
2.1.0-rc.0
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-beta.2
2.0.0-beta.1
2.0.0-beta.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.16.0
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.12.0-rc
0.11.3
0.11.2
0.11.1
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.0
0.5.1
0.5.0
0.4.0
0.3.0
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.0
A toolkit for data mapping and language integrated query for Elixir
Current section
Files
Jump to
Current section
Files
lib/ecto/schema.ex
defmodule Ecto.Schema do
@moduledoc ~S"""
Defines a schema for a model.
A schema is a struct with associated metadata that is persisted to a
repository. Every schema model is also a struct, that means that you work
with models just like you would work with structs.
## Example
defmodule User do
use Ecto.Schema
schema "users" do
field :name, :string
field :age, :integer, default: 0
has_many :posts, Post
end
end
By default, a schema will generate both a primary key named `id`
of type `:integer` and `belongs_to` associations will generate
foreign keys of type `:integer` too. Those setting can be configured
below.
## Schema attributes
The schema supports some attributes to be set before hand,
configuring the defined schema.
Those attributes are:
* `@primary_key` - configures the schema primary key. It expects
a tuple with the primary key name, type and options. Defaults
to `{:id, :integer, []}`. When set to false, does not define
a primary key in the model;
* `@foreign_key_type` - configures the default foreign key type
used by `belongs_to` associations. Defaults to `:integer`;
* `@timestamps_type` - configures the default timestamps type
used by `timestamps`. Defaults to `:datetime`;
* `@derive` - the same as `@derive` available in `Kernel.defstruct/1`
as the schema defines a struct behind the scenes;
The advantage of defining configure the schema via those attributes
is that they can be set with a macro to configure application wide
defaults. For example, if you would like to use `uuid`'s in all of
your application models, you can do:
# Define a module to be used as base
defmodule MyApp.Model do
defmacro __using__(_) do
quote do
use Ecto.Model
@primary_key {:id, :uuid, []}
@foreign_key_type :uuid
end
end
end
# Now use MyApp.Model to define new models
defmodule MyApp.Comment do
use MyApp.Model
schema "comments" do
belongs_to :post, MyApp.Post
end
end
Any models using `MyApp.Model will get the `:id` field with type
`:uuid` as primary key.
The `belongs_to` association on `MyApp.Comment` will also define
a `:post_id` field with `:uuid` type that references the `:id` of
the `MyApp.Post` model.
## Types and casting
When defining the schema, types need to be given. Those types are
specific to Ecto and must be one of:
Ecto type | Elixir type | Literal syntax in query
:---------------------- | :---------------------- | :---------------------
`:integer` | `integer` | 1, 2, 3
`:float` | `float` | 1.0, 2.0, 3.0
`:boolean` | `boolean` | true, false
`:string` | UTF-8 encoded `binary` | "hello"
`:binary` | `binary` | `<<int, int, int, ...>>`
`:uuid` | 16 byte `binary` | `uuid(binary_or_string)`
`{:array, inner_type}` | `list` | `[value, value, value, ...]`
`:decimal` | [`Decimal`](https://github.com/ericmj/decimal)
`:datetime` | `%Ecto.DateTime{}`
`:date` | `%Ecto.Date{}`
`:time` | `%Ecto.Time{}`
Models can also have virtual fields by passing the `virtual: true`
option. These fields are not persisted to the database and can
optionally not be type checked by declaring type `:any`.
When directly manipulating the struct, it is the responsibility of
the developer to ensure the field values have the proper type. For
example, you can create a weather struct with an invalid value
for `temp_lo`:
iex> weather = %Weather{temp_lo: "0"}
iex> weather.temp_lo
"0"
However, if you attempt to persist the struct above, an error will
be raised since Ecto validates the types when building the query.
Therefore, when working and manipulating external data, it is
recommended the usage of `Ecto.Changeset`'s that are able to filter
and properly cast external data. In fact, `Ecto.Changeset` and custom
types provide a powerful combination to extend Ecto types and queries.
## Custom types
Besides the types mentioned above, Ecto allows custom types to be
defined. A custom type is a module that implements the `Ecto.Type`
behaviour. Read the `Ecto.Type` documentation for more information
on how to implement them.
## Reflection
Any schema module will generate the `__schema__` function that can be
used for runtime introspection of the schema:
* `__schema__(:source)` - Returns the source as given to `schema/2`;
* `__schema__(:primary_key)` - Returns the field that is the primary
key or `nil` if there is none;
* `__schema__(:fields)` - Returns a list of all non-virtual field names;
* `__schema__(:field, field)` - Returns the type of the given non-virtual field;
* `__schema__(:associations)` - Returns a list of all association field names;
* `__schema__(:association, assoc)` - Returns the association reflection of the given assoc;
* `__schema__(:read_after_writes)` - Non-virtual fields that must be read back
from the database after every write (insert or update);
* `__schema__(:load, struct \\ __struct__(), fields_or_idx, values)` - Loads a
new model struct from a tuple of non-virtual field values starting at the given
index or defined by the given fields;
Furthermore, both `__struct__` and `__changeset__` functions are
defined so structs and changeset functionalities are available.
"""
@doc false
defmacro __using__(_) do
quote do
import Ecto.Schema, only: [schema: 2]
@primary_key {:id, :integer, []}
@timestamps_type :datetime
@foreign_key_type :integer
end
end
@doc """
Defines a schema with a source name and field definitions.
"""
defmacro schema(source, [do: block]) do
quote do
source = unquote(source)
unless is_binary(source) do
raise ArgumentError, "schema source must be a string, got: #{inspect source}"
end
Module.register_attribute(__MODULE__, :changeset_fields, accumulate: true)
Module.register_attribute(__MODULE__, :struct_fields, accumulate: true)
Module.register_attribute(__MODULE__, :ecto_fields, accumulate: true)
Module.register_attribute(__MODULE__, :ecto_assocs, accumulate: true)
Module.register_attribute(__MODULE__, :ecto_raw, accumulate: true)
primary_key_field =
case @primary_key do
false ->
nil
{name, type, opts} ->
Ecto.Schema.field(name, type, opts)
name
other ->
raise ArgumentError, "@primary_key must be false or {name, type, opts}"
end
try do
import Ecto.Schema
unquote(block)
after
:ok
end
fields = @ecto_fields |> Enum.reverse
assocs = @ecto_assocs |> Enum.reverse
Module.eval_quoted __MODULE__, [
Ecto.Schema.__struct__(@struct_fields),
Ecto.Schema.__changeset__(@changeset_fields),
Ecto.Schema.__source__(source),
Ecto.Schema.__fields__(fields),
Ecto.Schema.__assocs__(__MODULE__, assocs, primary_key_field, fields),
Ecto.Schema.__primary_key__(primary_key_field),
Ecto.Schema.__load__(fields),
Ecto.Schema.__read_after_writes__(primary_key_field, @ecto_raw)]
end
end
## API
@doc """
Defines a field on the model schema with given name and type.
## Options
* `:default` - Sets the default value on the schema and the struct
* `:virtual` - When true, the field is not persisted
* `:read_after_writes` - When true, the field is always read back
from the repository after inserts and updates
"""
defmacro field(name, type \\ :string, opts \\ []) do
quote do
Ecto.Schema.__field__(__MODULE__, unquote(name), unquote(type), unquote(opts))
end
end
@doc """
Generates `:inserted_at` and `:updated_at` timestamp fields.
When using `Ecto.Model`, the fields generated by this macro
will automatically be set to the current time when inserting
and updating values in a repository.
## Options
* `:type` - the timestamps type, defaults to `:datetime`.
Can also be set via the `@timestamps_type` attribute
* `:inserted_at` - the name of the column for insertion times or `false`
* `:updated_at` - the name of the column for update times or `false`
"""
defmacro timestamps(opts \\ []) do
quote bind_quoted: binding do
timestamps = Keyword.merge [inserted_at: :inserted_at,
updated_at: :updated_at,
type: @timestamps_type], opts
if inserted_at = Keyword.fetch!(timestamps, :inserted_at) do
Ecto.Schema.field(inserted_at, timestamps[:type], [])
end
if updated_at = Keyword.fetch!(timestamps, :updated_at) do
Ecto.Schema.field(updated_at, timestamps[:type], [])
end
@timestamps timestamps
end
end
@doc """
Defines an association.
This macro is used by `belongs_to/3`, `has_one/3` and `has_many/3` to
define associations. However, custom association mechanisms can be provided
by developers and hooked in via this macro.
Read more about custom associations in `Ecto.Associations`.
"""
defmacro association(name, association, opts \\ []) do
quote do
Ecto.Schema.__association__(__MODULE__, unquote(name), unquote(association), unquote(opts))
end
end
@doc ~S"""
Indicates a one-to-many association with another model.
The current model has zero or more records of the other model. The other
model often has a `belongs_to` field with the reverse association.
## Options
* `:foreign_key` - Sets the foreign key, this should map to a field on the
other model, defaults to the underscored name of the current model
suffixed by `_id`
* `:references` - Sets the key on the current model to be used for the
association, defaults to the primary key on the model;
## Examples
defmodule Post do
schema "posts" do
has_many :comments, Comment
end
end
# Get all comments for a given post
post = Repo.get(Post, 42)
comments = Repo.all assoc(post, :comments)
# The comments can come preloaded on the post struct
[post] = Repo.all(from(p in Post, where: p.id == 42, preload: :comments))
post.comments #=> [ %Comment{...}, ... ]
"""
defmacro has_many(name, queryable, opts \\ []) do
quote bind_quoted: binding() do
association(name, Ecto.Associations.Has,
[queryable: queryable, cardinality: :many] ++ opts)
end
end
@doc ~S"""
Indicates a one-to-one association with another model.
The current model has zero or one records of the other model. The other
model often has a `belongs_to` field with the reverse association.
## Options
* `:foreign_key` - Sets the foreign key, this should map to a field on the
other model, defaults to the underscored name of the current model
suffixed by `_id`
* `:references` - Sets the key on the current model to be used for the
association, defaults to the primary key on the model
## Examples
defmodule Post do
schema "posts" do
has_one :permalink, Permalink
end
end
# The permalink can come preloaded on the post struct
[post] = Repo.all(from(p in Post, where: p.id == 42, preload: :permalink))
post.permalink #=> %Permalink{...}
"""
defmacro has_one(name, queryable, opts \\ []) do
quote bind_quoted: binding() do
association(name, Ecto.Associations.Has,
[queryable: queryable, cardinality: :one] ++ opts)
end
end
@doc ~S"""
Indicates a one-to-one association with another model.
The current model belongs to zero or one records of the other model. The other
model often has a `has_one` or a `has_many` field with the reverse association.
You should use `belongs_to` in the table that contains the foreign key. Imagine
a company <-> manager relationship. If the company contains the `manager_id` in
the underlying database table, we say the company belongs to manager.
In fact, when you invoke this macro, a field with the name of foreign key is
automatically defined in the schema for you.
## Options
* `:foreign_key` - Sets the foreign key field name, defaults to the name
of the association suffixed by `_id`. For example, `belongs_to :company`
will define foreign key of `:company_id`
* `:references` - Sets the key on the other model to be used for the
association, defaults to: `:id`
* `:auto_field` - When false, does not automatically define a `:foreign_key`
field, implying the user is defining the field manually elsewhere
* `:type` - Sets the type of automtically defined `:foreign_key`.
Defaults to: `:integer` and be set per schema via `@foreign_key_type`
All other options are forwarded to the underlying foreign key definition
and therefore accept the same options as `field/3`.
## Examples
defmodule Comment do
schema "comments" do
# This automatically defines a post_id field too
belongs_to :post, Post
end
end
# The post can come preloaded on the comment record
[comment] = Repo.all(from(c in Comment, where: c.id == 42, preload: :post))
comment.post #=> %Post{...}
"""
defmacro belongs_to(name, queryable, opts \\ []) do
quote bind_quoted: binding() do
opts = Keyword.put_new(opts, :foreign_key, :"#{name}_id")
foreign_key_type = opts[:type] || @foreign_key_type
if Keyword.get(opts, :auto_field, true) do
field(opts[:foreign_key], foreign_key_type, opts)
end
association(name, Ecto.Associations.BelongsTo, [queryable: queryable] ++ opts)
end
end
## Callbacks
@doc false
def __field__(mod, name, type, opts) do
check_type!(type, opts[:virtual])
check_default!(type, opts[:default])
Module.put_attribute(mod, :changeset_fields, {name, type})
put_struct_field(mod, name, opts[:default])
unless opts[:virtual] do
if opts[:read_after_writes] do
Module.put_attribute(mod, :ecto_raw, name)
end
Module.put_attribute(mod, :ecto_fields, {name, type, opts})
end
end
@doc false
def __association__(mod, name, association, opts) do
put_struct_field(mod, name,
%Ecto.Associations.NotLoaded{__owner__: mod, __field__: name})
Module.put_attribute(mod, :ecto_assocs, {name, association, opts})
end
@doc false
def __load__(struct, fields, idx, values) when is_integer(idx) and is_tuple(values) do
Enum.reduce(fields, {struct, idx}, fn
{field, type}, {acc, idx} ->
value = Ecto.Type.load!(type, elem(values, idx))
{Map.put(acc, field, value), idx + 1}
end) |> elem(0)
end
def __load__(struct, fields, keys, values) when is_list(keys) and is_tuple(values) do
Enum.reduce(keys, {struct, 0}, fn
field, {acc, idx} ->
value = Ecto.Type.load!(Keyword.fetch!(fields, field), elem(values, idx))
{Map.put(acc, field, value), idx + 1}
end) |> elem(0)
end
## Quoted callbacks
@doc false
def __changeset__(changeset_fields) do
map = changeset_fields |> Enum.into(%{}) |> Macro.escape()
quote do
def __changeset__, do: unquote(map)
end
end
@doc false
def __struct__(struct_fields) do
quote do
defstruct unquote(Macro.escape(struct_fields))
end
end
@doc false
def __source__(source) do
quote do
def __schema__(:source), do: unquote(Macro.escape(source))
end
end
@doc false
def __fields__(fields) do
quoted = Enum.map(fields, fn {name, type, _opts} ->
quote do
def __schema__(:field, unquote(name)), do: unquote(type)
end
end)
field_names = Enum.map(fields, &elem(&1, 0))
quoted ++ [quote do
def __schema__(:field, _), do: nil
def __schema__(:fields), do: unquote(field_names)
end]
end
@doc false
def __assocs__(module, assocs, primary_key, fields) do
fields = Enum.map(fields, &elem(&1, 0))
quoted =
Enum.map(assocs, fn {name, type, opts} ->
refl = type.struct(name, module, primary_key, fields, opts)
quote do
def __schema__(:association, unquote(name)) do
unquote(Macro.escape(refl))
end
end
end)
assoc_names = Enum.map(assocs, &elem(&1, 0))
quote do
def __schema__(:associations), do: unquote(assoc_names)
unquote(quoted)
def __schema__(:association, _), do: nil
end
end
@doc false
def __primary_key__(primary_key) do
quote do
def __schema__(:primary_key), do: unquote(primary_key)
end
end
@doc false
def __load__(fields) do
fields = Enum.map(fields, fn {name, type, _opts} -> {name, type} end)
quote do
def __schema__(:load, struct \\ __struct__(), fields_or_idx, values) do
Ecto.Schema.__load__(struct, unquote(fields), fields_or_idx, values)
end
end
end
@doc false
def __read_after_writes__(primary_key, fields) do
if primary_key do
fields = [primary_key|List.delete(fields, primary_key)]
end
quote do
def __schema__(:read_after_writes), do: unquote(fields)
end
end
## Private
defp put_struct_field(mod, name, assoc) do
fields = Module.get_attribute(mod, :struct_fields)
if List.keyfind(fields, name, 0) do
raise ArgumentError, "field/association `#{name}` is already set on schema"
end
Module.put_attribute(mod, :struct_fields, {name, assoc})
end
defp check_type!(type, virtual?) do
cond do
type == :any and not virtual? ->
raise ArgumentError, "only virtual fields can have type :any"
Ecto.Type.primitive?(type) ->
true
is_atom(type) ->
if Code.ensure_compiled?(type) and function_exported?(type, :type, 0) do
type
else
raise ArgumentError, "invalid or unknown field type `#{inspect type}`"
end
true ->
raise ArgumentError, "invalid field type `#{inspect type}`"
end
end
defp check_default!(type, default) do
case Ecto.Type.dump(type, default) do
{:ok, _} ->
:ok
:error ->
raise ArgumentError, "invalid default argument `#{inspect default}` for `#{inspect type}`"
end
end
end