Ecto.Changeset
Changesets allow filtering, casting and validation of model changes.
There is an example of working with changesets in the introductory
documentation in the Ecto
module.
The Ecto.Changeset struct
The fields are:
valid?
- Stores if the changeset is validrepo
- The repository applying the changeset (only set after a Repo function is called)model
- The changeset root modelparams
- The parameters as given on changeset creationchanges
- Thechanges
from parameters that were approved in castingerrors
- All errors from validationsvalidations
- All validations performed in the changesetrequired
- All required fields as a list of atomsoptional
- All optional fields as a list of atoms
Summary↑
add_error(changeset, key, error) | Adds an error to the changeset |
apply(changeset) | Applies the changeset changes to its model |
cast(params, model_or_changeset, required, optional \\ []) | Converts the given |
change(model, changes \\ %{}) | Generates a changeset to change the given |
delete_change(changeset, key) | Deletes a change with the given key |
fetch_change(changeset, key) | Fetches a change |
fetch_field(changeset, key) | Fetches the given field from changes or from the model |
get_change(changeset, key, default \\ nil) | Gets a change or returns default value |
get_field(changeset, key, default \\ nil) | Gets a field from changes or from the model |
merge(changeset1, changeset2) | Merges two changesets |
put_change(changeset, key, value) | Puts a change on the given key with value |
put_new_change(changeset, key, value) | Puts a change on the given key only if a change with that key doesn't already exist |
update_change(changeset, key, function) | Updates a change |
validate_change(changeset, field, validator) | Validates the given |
validate_change(changeset, field, metadata, validator) | Stores the validation |
validate_exclusion(changeset, field, data) | Validates a change is not in the enumerable |
validate_format(changeset, field, format) | Validates a change has the given format |
validate_inclusion(changeset, field, data) | Validates a change is included in the enumerable |
validate_length(changeset, field, opts) | Validates a change is a string of the given length |
validate_unique(changeset, field, opts) | Validates |
Types ↑
error :: {atom, atom | {atom, [term]}}
t :: %Ecto.Changeset{valid?: boolean, repo: atom | nil, model: Ecto.Model.t | nil, params: %{String.t => term} | nil, changes: %{atom => term}, required: [atom], optional: [atom], errors: [error], validations: [{atom, atom | {atom, [term]}}]}
Functions
Specs:
Adds an error to the changeset.
Examples
add_error(changeset, :name, :invalid)
Specs:
- apply(t) :: Ecto.Model.t
Applies the changeset changes to its model
Note this operation is automatically performed on Ecto.Repo.insert/2
and
Ecto.Repo.update/2
, however this function is provided for
debugging and testing purposes.
Examples
apply(changeset)
Specs:
- cast(%{binary => term} | %{atom => term} | nil, Ecto.Model.t | Ecto.Changeset.t, [String.t | atom], [String.t | atom]) :: t
Converts the given params
into a changeset for model
keeping only the set of required
and optional
keys.
This functions receives the params
and cast them according
to the schema information from model
. params
is a map of
string keys or a map with atom keys containing potentially
unsafe data.
During casting, all valid parameters will have their key name
converted to atoms and stored as a change in the changeset.
All other parameters that are not listed in required
or
optional
are ignored.
If casting of all fields is successful and all required fields are present either in the model or in the given params, the changeset is returned as valid.
No parameters
The params
argument can also be nil. In such cases, the
changeset is automatically marked as invalid, with an empty
changes map. This is useful to run the changeset through
all validation steps for introspection.
Composing casts
cast/4
also accepts a changeset instead of a model as its second argument.
In such cases, all the effects caused by the call to cast/4
(additional and
optional fields, errors and changes) are simply added to the ones already
present in the argument changeset. Parameters are merged (not deep-merged)
and the ones passed to cast/4
take precedence over the ones already in the
changeset.
Note that if a field is marked both as required as well as optional (for
example by being in the :required
field of the argument changeset and also
in the optional
list passed to cast/4
), then it will be marked as required
and not optional). This represents the fact that required fields are
"stronger" than optional fields.
Examples
iex> changeset = cast(params, post, ~w(title), ~w())
iex> if changeset.valid? do
...> Repo.update(changeset)
...> end
Passing a changeset as the second argument:
iex> changeset = cast(%{title: "Hello"}, post, ~w(), ~w(title))
iex> new_changeset = cast(%{title: "Foo", body: "Bar"}, ~w(title), ~w(body))
iex> new_changeset.params
%{title: "Foo", body: "Bar"}
iex> new_changeset.required
[:title]
iex> new_changeset.optional
[:body]
Specs:
- change(Ecto.Model.t, %{atom => term}) :: t
Generates a changeset to change the given model
.
This function is useful for directly changing the model, without performing casting nor validation.
For this reason, changes
expect the keys to be atoms.
See cast/4
if you'd prefer to cast and validate external
parameters.
Examples
iex> changeset = change(post, title: "new title")
iex> Repo.update(changeset)
%Post{...}
Specs:
Deletes a change with the given key.
Specs:
- fetch_change(t, atom) :: {:ok, term} | :error
Fetches a change.
Specs:
- fetch_field(t, atom) :: {:changes, term} | {:model, term} | :error
Fetches the given field from changes or from the model.
While fetch_change/2
only looks at the current changes
to retrieve a value, this function looks at the changes and
then falls back on the model, finally returning :error
if
no value is available.
Specs:
- get_change(t, atom, term) :: term
Gets a change or returns default value.
Specs:
- get_field(t, atom, term) :: term
Gets a field from changes or from the model.
While get_change/3
only looks at the current changes
to retrieve a value, this function looks at the changes and
then falls back on the model, finally returning default
if
no value is available.
Specs:
Merges two changesets.
This function merges two changesets provided they have been applied to the
same model (their :model
field is equal); if the models differ, an
ArgumentError
exception is raised. If one of the changesets has a :repo
field which is not nil
, then the value of that field is used as the :repo
field of the resulting changeset; if both changesets have a non-nil
and
different :repo
field, an ArgumentError
exception is raised.
The other fields are merged with the following criteria:
params
- params are merged (not deep-merged) giving precedence to the params ofchangeset2
in case of a conflict. If either changeset has its:params
field set tonil
, the resulting changeset will have its params set tonil
too.changes
- changes are merged giving precedence to thechangeset2
changes.errors
andvalidations
- they are simply concatenated.required
andoptional
- they are merged; all the fields that appear in the optional list of either changesets and also in the required list of the other changeset are moved to the required list of the resulting changeset.
Examples
iex> changeset1 = cast(%{title: "Title"}, %Post{}, ~w(title), ~w(body))
iex> changeset2 = cast(%{title: "New title", body: "Body"}, %Post{}, ~w(title body), ~w())
iex> changeset = merge(changeset1, changeset2)
iex> changeset.changes
%{body: "Body", title: "New title"}
iex> changeset.required
[:title, :body]
iex> changeset.optional
[]
iex> changeset1 = cast(%{title: "Title"}, %Post{body: "Body"}, ~w(title), ~w(body))
iex> changeset2 = cast(%{title: "New title"}, %Post{}, ~w(title), ~w())
iex> merge(changeset1, changeset2)
** (ArgumentError) different models when merging changesets
Specs:
Puts a change on the given key with value.
Specs:
Puts a change on the given key only if a change with that key doesn't already exist.
Examples
iex> changeset = put_new_change(changeset, :title, "foo")
iex> changeset.changes.title
"foo"
iex> changeset = put_new_change(changeset, :title, "bar")
iex> changeset.changes.title
"foo"
Specs:
Updates a change.
The function
is invoked with the change value only if there
is a change for the given key
. Notice the value of the change
can still be nil (unless the field was marked as required on cast/4
).
Specs:
Validates the given field
change.
It invokes the validator
function to perform the validation
only if a change for the given field
exists and the change
value is not nil. The function must a list of errors (empty
meaning no errors).
In case of at least one error, they will be stored in the
errors
field of the changeset and the valid?
flag will
be set to false.
Specs:
Stores the validation metadata
and validates the given field
change.
Similar to validate_change/3
but stores the validation metadata
into the changeset validators. The validator metadata is often used
as a reflection mechanism, to automatically generate code based on
the available validations.
Specs:
Validates a change is not in the enumerable.
Examples
validate_exclusion(changeset, :name, ~w(admin superadmin))
Specs:
Validates a change has the given format.
Examples
validate_format(changeset, :email, ~r/@/)
Specs:
Validates a change is included in the enumerable.
Examples
validate_inclusion(changeset, :gender, ["male", "female", "who cares?"])
validate_inclusion(changeset, :age, 0..99)
Specs:
Validates a change is a string of the given length.
Examples
validate_length(changeset, :title, 3..100)
validate_length(changeset, :title, min: 3)
validate_length(changeset, :title, max: 100)
validate_length(changeset, :code, is: 9)
Specs:
Validates field
's uniqueness on Repo
.
Examples
validate_unique(changeset, :email, on: Repo)
Options
:on
- the repository to perform the query on:downcase
- when true, downcase values when performing the uniqueness query
Case sensitivity
Unfortunately, different databases provide different guarantees
when it comes to case sensitive. For example, in MySQL, comparisons
are case insensitive. In Postgres, users can define case insensitive
column by using the :citext
type/extension.
Those facts make it hard for Ecto to guarantee if the unique
validation is case insensitive or not and therefore it does not
provide a :case_sensitive
option.
However this function does provide a :downcase
option that
guarantees values are downcased when doing the uniqueness check.
When you set this option, values are downcased regardless of the
database you are using.
Since the :downcase
option downcases the database values on the
fly, use it with care as it may affect performance. For example,
if you must use this option, you may want to set an index with the
downcased value. Using Ecto.Migration
syntax, one could write:
create index(:posts, ["lower(title)"])
Many times though, you don't even need to use the downcase option
at validate_unique/3
and instead you can explicitly downcase
values before inserting them into the database:
cast(params, model, ~w(email), ~w())
|> update_change(:email, &String.downcase/1)
|> validate_unique(:email, on: Repo)