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:

Source

Summary

add_error(changeset, key, error)

Adds an error to the changeset

apply_changes(changeset)

Applies the changeset changes to the changeset model

cast(model_or_changeset, params, required, optional \\ [])

Converts the given params into a changeset for model keeping only the set of required and optional keys

change(model_or_changeset, changes \\ %{})

Wraps the given model in a changeset or adds changes to a changeset

delete_change(changeset, key)

Deletes a change with the given key

fetch_change(changeset, key)

Fetches a change from the given changeset

fetch_field(changeset, key)

Fetches the given field from changes or from the model

force_change(changeset, key, value)

Puts a change on the given key with value

get_change(changeset, key, default \\ nil)

Gets a change or returns a 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, also, if the change has the same value as the model, it is not added to the list of changes

update_change(changeset, key, function)

Updates a change

validate_change(changeset, field, validator)

Validates the given field change

validate_change(changeset, field, metadata, validator)

Stores the validation metadata and validates the given field change

validate_confirmation(changeset, field, opts \\ [])

Validates that the given field matches the confirmation parameter of that field

validate_exclusion(changeset, field, data, opts \\ [])

Validates a change is not included in given the enumerable

validate_format(changeset, field, format, opts \\ [])

Validates a change has the given format

validate_inclusion(changeset, field, data, opts \\ [])

Validates a change is included in the given enumerable

validate_length(changeset, field, opts)

Validates a change is a string of the given length

validate_number(changeset, field, opts)

Validates the properties of a number

validate_subset(changeset, field, data, opts \\ [])

Validates a change, of type enum, is a subset of the given enumerable. Like validate_inclusion/4 for lists

validate_unique(changeset, field, opts)

Validates the given field‘s uniqueness on the given repository

Types

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, String.t | {String.t, [term]}}], filters: %{atom => term}}

error :: {atom, error_message}

error_message :: String.t | {String.t, integer}

Functions

add_error(changeset, key, error)

Specs:

Adds an error to the changeset.

Examples

iex> changeset = change(%Post{}, %{title: ""})
iex> changeset = add_error(changeset, :title, "empty")
iex> changeset.errors
[title: "empty"]
iex> changeset.valid?
false
Source
apply_changes(changeset)

Specs:

Applies the changeset changes to the changeset 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_changes(changeset)
Source
cast(model_or_changeset, params, required, optional \\ [])

Specs:

Converts the given params into a changeset for model keeping only the set of required and optional keys.

This functions receives a model and some params, and casts the params according to the schema information from model. params is a map with string keys or a map with atom keys containing potentially unsafe data.

During casting, all valid parameters will have their key name converted to an atom and stored as a change in the :changes field of the changeset. All 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.

Empty parameters

The params argument can also be the atom :empty. 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 first 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(post, params, ~w(title), ~w())
iex> if changeset.valid? do
...>   Repo.update!(changeset)
...> end

Passing a changeset as the first argument:

iex> changeset = cast(post, %{title: "Hello"}, ~w(), ~w(title))
iex> new_changeset = cast(changeset, %{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]
Source
change(model_or_changeset, changes \\ %{})

Specs:

Wraps the given model in a changeset or adds changes to a changeset.

Changed attributes will only be added if the change does not have the same value as the attribute in the model.

This function is useful for:

  • wrapping a model inside a changeset
  • directly changing the model without performing castings nor validations
  • directly bulk-adding changes to a changeset

Since no validation nor casting is performed, change/2 expects the keys in changes to be atoms. changes can be a map as well as a keyword list.

When a changeset is passed as the first argument, the changes passed as the second argument are merged over the changes already in the changeset if they differ from the values in the model. If changes is an empty map, this function is a no-op.

See cast/4 if you’d prefer to cast and validate external parameters.

Examples

iex> changeset = change(%Post{})
%Ecto.Changeset{...}
iex> changeset.valid?
true
iex> changeset.changes
%{}

iex> changeset = change(%Post{author: "bar"}, title: "title")
iex> changeset.changes
%{title: "title"}

iex> changeset = change(%Post{title: "title"}, title: "title")
iex> changeset.changes
%{}

iex> changeset = change(changeset, %{title: "new title", body: "body"})
iex> changeset.changes.title
"new title"
iex> changeset.changes.body
"body"
Source
delete_change(changeset, key)

Specs:

  • delete_change(t, atom) :: t

Deletes a change with the given key.

Examples

iex> changeset = change(%Post{}, %{title: "foo"})
iex> changeset = delete_change(changeset, :title)
iex> get_change(changeset, :title)
nil
Source
fetch_change(changeset, key)

Specs:

  • fetch_change(t, atom) :: {:ok, term} | :error

Fetches a change from the given changeset.

This function only looks at the :changes field of the given changeset and returns {:ok, value} if the change is present or :error if it’s not.

Examples

iex> changeset = change(%Post{body: "foo"}, %{title: "bar"})
iex> fetch_change(changeset, :title)
{:ok, "bar"}
iex> fetch_change(changeset, :body)
:error
Source
fetch_field(changeset, key)

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.

Examples

iex> post = %Post{title: "Foo", body: "Bar baz bong"}
iex> changeset = change(post, %{title: "New title"})
iex> fetch_field(changeset, :title)
{:changes, "New title"}
iex> fetch_field(changeset, :body)
{:model, "Bar baz bong"}
iex> fetch_field(changeset, :not_a_field)
:error
Source
force_change(changeset, key, value)

Specs:

  • force_change(t, atom, term) :: t

Puts a change on the given key with value.

If the change is already present, it is overridden with the new value.

Examples

iex> changeset = change(%Post{author: "bar"}, %{title: "foo"})
iex> changeset = put_change(changeset, :title, "bar")
iex> changeset.changes
%{title: "bar"}

iex> changeset = put_change(changeset, :author, "bar")
iex> changeset.changes
%{title: "bar", author: "bar"}
Source
get_change(changeset, key, default \\ nil)

Specs:

  • get_change(t, atom, term) :: term

Gets a change or returns a default value.

Examples

iex> changeset = change(%Post{body: "foo"}, %{title: "bar"})
iex> get_change(changeset, :title)
"bar"
iex> get_change(changeset, :body)
nil
Source
get_field(changeset, key, default \\ nil)

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.

iex> post = %Post{title: "A title", body: "My body is a cage"}
iex> changeset = change(post, %{title: "A new title"})
iex> get_field(changeset, :title)
"A new title"
iex> get_field(changeset, :not_a_field, "Told you, not a field!")
"Told you, not a field!"
Source
merge(changeset1, changeset2)

Specs:

  • merge(t, t) :: t

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 of changeset2 in case of a conflict. If either changeset has its :params field set to nil, the resulting changeset will have its params set to nil too.
  • changes - changes are merged giving precedence to the changeset2 changes.
  • errors and validations - they are simply concatenated.
  • required and optional - 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
Source
put_change(changeset, key, value)

Specs:

  • put_change(t, atom, term) :: t

Puts a change on the given key with value.

If the change is already present, it is overridden with the new value, also, if the change has the same value as the model, it is not added to the list of changes.

Examples

iex> changeset = change(%Post{author: "bar"}, %{title: "foo"})
iex> changeset = put_change(changeset, :title, "bar")
iex> changeset.changes
%{title: "bar"}

iex> changeset = put_change(changeset, :author, "bar")
iex> changeset.changes
%{title: "bar"}
Source
put_new_change(changeset, key, value)

Specs:

  • put_new_change(t, atom, term) :: t

Puts a change on the given key only if a change with that key doesn’t already exist, also, if the change has the same value as the model, it is not added to the list of changes.

Examples

iex> changeset = change(%Post{author: "bar"}, %{})
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"}

iex> changeset = put_new_change(changeset, :author, "bar")
iex> changeset.changes
%{title: "foo"}
Source
update_change(changeset, key, function)

Specs:

  • update_change(t, atom, (term -> term)) :: t

Updates a change.

The given function is invoked with the change value only if there is a change for the given key. Note that the value of the change can still be nil (unless the field was marked as required on cast/4).

Examples

iex> changeset = change(%Post{}, %{impressions: 1})
iex> changeset = update_change(changeset, :impressions, &(&1 + 1))
iex> changeset.changes.impressions
2
Source
validate_change(changeset, field, validator)

Specs:

  • validate_change(t, atom, (atom, term -> [error])) :: t

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 return a list of errors (with an empty list meaning no errors).

In case there’s at least one error, the list of errors will be appended to the :errors field of the changeset and the :valid? flag will be set to false.

Examples

iex> changeset = change(%Post{}, %{title: "foo"})
iex> changeset = validate_change changeset, :title, fn
...>   # Value must not be "foo"!
...>   :title, "foo" -> [title: "is_foo"]
...>   :title, _     -> []
...> end
iex> changeset.errors
[title: "is_foo"]
Source
validate_change(changeset, field, metadata, validator)

Specs:

  • validate_change(t, atom, any, (atom, term -> [error])) :: t

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.

Examples

iex> changeset = change(%Post{}, %{title: "foo"})
iex> changeset = validate_change changeset, :title, :useless_validator, fn
...>   _, _ -> []
...> end
iex> changeset.validations
[title: :useless_validator]
Source
validate_confirmation(changeset, field, opts \\ [])

Specs:

  • validate_confirmation(t, atom, Enum.t) :: t

Validates that the given field matches the confirmation parameter of that field.

By calling validate_confirmation(changeset, :email), this validation will check if both “email” and “email_confirmation” in the parameter map matches.

Note that this does not add a validation error if the confirmation field is nil. Note “email_confirmation” does not need to be added as a virtual field in your schema.

Options

  • :message - the message on failure, defaults to “does not match”

Examples

validate_confirmation(changeset, :email)
validate_confirmation(changeset, :password, message: "passwords do not match")

cast(params, model, ~w(password), ~w())
|> validate_confirmation(:password, message: "passwords do not match")
Source
validate_exclusion(changeset, field, data, opts \\ [])

Validates a change is not included in given the enumerable.

Options

  • :message - the message on failure, defaults to “is reserved”

Examples

validate_exclusion(changeset, :name, ~w(admin superadmin))
Source
validate_format(changeset, field, format, opts \\ [])

Validates a change has the given format.

The format has to be expressed as a regular expression.

Options

  • :message - the message on failure, defaults to “has invalid format”

Examples

validate_format(changeset, :email, ~r/@/)
Source
validate_inclusion(changeset, field, data, opts \\ [])

Validates a change is included in the given enumerable.

Options

  • :message - the message on failure, defaults to “is invalid”

Examples

validate_inclusion(changeset, :gender, ["man", "woman", "other", "prefer not to say"])
validate_inclusion(changeset, :age, 0..99)
Source
validate_length(changeset, field, opts)

Specs:

Validates a change is a string of the given length.

Options

  • :is - the string length must be exactly this value
  • :min - the string length must be greater than or equal to this value
  • :max - the string lenght must be less than or equal to this value
  • :message - the message on failure, depending on the validation, is one of:

    • “should be %{count} characters”
    • “should be at least %{count} characters”
    • “should be at most %{count} characters”

Examples

validate_length(changeset, :title, min: 3)
validate_length(changeset, :title, max: 100)
validate_length(changeset, :title, min: 3, max: 100)
validate_length(changeset, :code, is: 9)
Source
validate_number(changeset, field, opts)

Specs:

Validates the properties of a number.

Options

  • :less_than
  • :greater_than
  • :less_than_or_equal_to
  • :greater_than_or_equal_to
  • :equal_to
  • :message - the message on failure, defaults to one of:

    • “must be less than %{count}”
    • “must be greater than %{count}”
    • “must be less than or equal to %{count}”
    • “must be greater than or equal to %{count}”
    • “must be equal to %{count}”

Examples

validate_number(changeset, :count, less_than: 3)
validate_number(changeset, :pi, greater_than: 3, less_than: 4)
validate_number(changeset, :the_answer_to_life_the_universe_and_everything, equal_to: 42)
Source
validate_subset(changeset, field, data, opts \\ [])

Validates a change, of type enum, is a subset of the given enumerable. Like validate_inclusion/4 for lists.

Options

  • :message - the message on failure, defaults to “#{x} is invalid”

Examples

validate_subset(changeset, :pets, ["cat", "dog", "parrot"])
validate_subset(changeset, :lottery_numbers, 0..99)
Source
validate_unique(changeset, field, opts)

Specs:

Validates the given field‘s uniqueness on the given repository.

The validation runs if the field (or any of the values given in scope) has changed and none of them contain an error. For this reason, you may want to trigger the unique validations as last in your validation pipeline.

Examples

validate_unique(changeset, :email, on: Repo)

Options

  • :message - the message on failure, defaults to “has already been taken”
  • :on - the repository to perform the query on
  • :downcase - when true, downcase values when performing the uniqueness query
  • :scope - a list of other fields to use for the uniqueness query

Case sensitivity

Unfortunately, different databases provide different guarantees when it comes to case-sensitiveness. For example, in MySQL, comparisons are case-insensitive by default. In Postgres, users can define case insensitive column by using the :citext type/extension.

These behaviours make it hard for Ecto to guarantee if the unique validation is case insensitive or not and that’s why Ecto does not provide a :case_sensitive option.

However validate_unique/3 does provide a :downcase option that guarantees values are downcased when doing the uniqueness check. When this option is set, values are downcased regardless of the database being used.

Since the :downcase option downcases the database values on the fly, it should be used with care as it may affect performance. For example, if this option is used, it could be appropriate to create an index with the downcased value. Using Ecto.Migration syntax, one could write:

create index(:posts, ["lower(title)"])

Many times, however, it’s simpler to just explicitly downcase values before inserting them into the database and avoid the :downcase option in validate_unique/3:

cast(params, model, ~w(email), ~w())
|> update_change(:email, &String.downcase/1)
|> validate_unique(:email, on: Repo)

Scope

The :scope option allows specifying of other fields that are used to limit the uniqueness check. For example, if our use case limits a user to a single comment per blog post, it would look something like:

cast(params, model, ~w(comment), ~w())
|> validate_unique(:user_id, scope: [:post_id], on: Repo)
Source