I'm playing with Elixir and the Phoenix web framework, but now I'm stuck on trying to validate a foreign key constraint. So, given a model Post
with many comments, I wrote the Comment
model as follows:
defmodule MyApp.Comment do
use MyAPp.Web, :model
schema "comments" do
field :body, :text
belongs_to :post, MyApp.Post
timestamps
end
@required_fields ~w(body post_id)
@optional_fields ~w()
def changeset(model, params \\ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
|> foreign_key_constraint(:post_id)
end
end
and its unit test:
defmodule MyApp.CommentTest do
# [...]
test "changeset with non existent post" do
attrs = %{
body: "A comment."
post_id: -1 # some non-existent id?
}
refute Comment.changeset(%Comment{}, attrs).valid?
assert {:post_id, "does not exist"} in errors_on(%Comment{}, %{})
end
end
According to http://hexdocs.pm/ecto/Ecto.Changeset.html#foreign_key_constraint/3:
The foreign key constraint works by relying on the database to check if the associated model exists or not. This is useful to guarantee that a child will only be created if the parent exists in the database too.
I expected that the code I wrote worked, instead it only checks for presence (as defined in @required_fields ~w(body post_id)
). I'm not excluding I did something wrong or misunderstood the statement in the docs.
Has anyone already stumbled upon this?
UPDATE: For completeness, here's the migration:
def change do
create table(:comments) do
add :body, :text
add :post_id, references(:posts)
timestamps
end
create index(:comments, [:post_id])
end
Since it relies on the database, you need to add the references in the migration and do the actual database operation. You must call Repo.insert/1
or Repo.update/1
giving your changeset and it will then return {:error, changeset}
.
Remember, there are no objects in Elixir nor in Ecto. Therefore changeset.valid?
could never perform a database operation, it is just data reflecting a set of changes to be performed and the state of this data transforms as you perform operations, like insert or update.
One final note, errors_on/2
is always going to return a new changeset and not the one you have been working with so far. Your last line should likely be:
assert {:post_id, "does not exist"} in changeset.errors
"relying on the database" means you need to have a FOREIGN KEY CONSTRAINT in your Database model.
In your migration, you should have had something like this:
create table(:comments) do
add :post_id, references(:posts)
end
which enforces a FOREIGN KEY CONSTRAINT CHECK between the parent and the child table.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With