According to the Phoenix documentation:
insert(Ecto.Schema.t | Ecto.Changeset.t, Keyword.t) :: {:ok, Ecto.Schema.t} | {:error, Ecto.Changeset.t}
Inserts a model or a changeset.
In case a model is given, the model is converted into a changeset with all model non-virtual fields as part of the changeset. This conversion is done by calling Ecto.Changeset.change/2 directly.
In case a changeset is given, the changes in the changeset are merged with the model fields, and all of them are sent to the database.
If any before_insert or after_insert callback is registered in the given model, they will be invoked with the changeset.
It returns {:ok, model} if the model has been successfully inserted or {:error, changeset} if there was a validation or a known constraint error.
defmodule Dollar.User do
use Dollar.Web, :model
schema "users" do
field :username, :string
field :sms_number, :string
field :email, :string
timestamps
end
@required_fields ~w(sms_number)
@optional_fields ~w(username email)
@doc """
Creates a changeset based on the `model` and `params`.
If no params are provided, an invalid changeset is returned
with no validation performed.
"""
def changeset(user, params \\ :empty) do
user
|> cast(params, @required_fields, @optional_fields)
|> validate_length(:sms_number, is: 10)
|> unique_constraint(:sms_number)
|> unique_constraint(:username)
end
end
Repo.insert(%User{})
=> {:error, ... }
I expected an error because the sms_number
is required.
[debug] INSERT INTO "users" ("inserted_at", "updated_at", "email", "sms_number", "username") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [{{2016, 3, 19}, {15, 13, 5, 0}}, {{2016, 3, 19}, {15, 13, 5, 0}}, nil, nil, nil] OK query=1.5ms
%Dollar.User{__meta__: #Ecto.Schema.Metadata<:loaded>, email: nil, id: 7,
inserted_at: #Ecto.DateTime<2016-03-19T15:13:05Z>, sms_number: nil,
updated_at: #Ecto.DateTime<2016-03-19T15:13:05Z>, username: nil}
The record is stored in the database
What am I doing wrong?
When you are trying to directly insert struct defined in model the changeset
function is not called at all. %User{}
is only a struct from a module and ecto does not know anything about functions defined in this module. You can delete changeset
function and Repo.insert
will still work.
changeset
function is your custom validation code. The docs you have pasted state that another function is called instead: Ecto.Changeset.change/2
. It creates a changeset that is valid by default and non of the fields are required.
You can try it yourself:
changeset = Ecto.Changeset.change %User{}
changeset.required # []
changeset.valid? # true
new_changeset = User.changeset changeset
new_changeset.required # [:sms_number]
new_changeset.valid? # false
Always run your custom validations before inserting to database and use Repo.insert
with a changeset instead of raw struct. It is probably also a good idea to enforce required fields on the database itself with not null constraint in your migration:
add :username, :string, null: false