Hello all,
I’m new to Elixir and Phoenix and I’m creating a simple API for a fake project management app (Tradtrack), just for practice.
The app has two schemas: user and project. One user has many projects, so the params to create a new project must include user_id. However, even if I pass a valid user_id when creating a project, I get this error:
errors: [
user_id: {"can't be blank", [validation: :required]}
],
Here are the user and project schemas and migrations.
- Project migration:
defmodule Tradtrack.Repo.Migrations.CreateProjectsTable do
use Ecto.Migration
def change do
create table :projects do
add :code, :string
add :client, :string
add :word_count, :integer
add :rate, :decimal
add :total, :decimal
add :date_received, :date
add :delivery_date, :utc_datetime
add :delivery_status, :boolean, default: false
add :payment_status, :boolean, default: false
add :notes, :string, default: ""
add :user_id, references(:users, type: :binary_id, on_delete: :delete_all)
timestamps()
end
create constraint(:projects, :word_count_must_be_greater_than_zero, check: "word_count > 0")
create constraint(:projects, :rate_must_be_greater_than_zero, check: "rate > 0")
end
end
- Project schema:
defmodule Tradtrack.Project do
use Ecto.Schema
import Ecto.Changeset
alias Ecto.Changeset
alias Tradtrack.User
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
@allowed [
:code,
:client,
:word_count,
:rate,
:date_received,
:delivery_date,
:delivery_status,
:payment_status,
:notes
]
@required_fields [:code, :client, :word_count, :rate, :date_received, :delivery_date, :user_id]
schema "projects" do
field :code, :string
field :client, :string
field :word_count, :integer
field :rate, :decimal
field :total, :decimal
field :date_received, :date
field :delivery_date, :utc_datetime
field :delivery_status, :boolean, default: false
field :payment_status, :boolean, default: false
field :notes, :string, default: ""
belongs_to :user, User, foreign_key: :user_id
timestamps()
end
def changeset(project \\ %__MODULE__{}, params) do
project
|> cast(params, @allowed)
|> validate_required(@required_fields)
|> check_constraint(:word_count, name: :word_count_must_be_greater_than_zero)
|> check_constraint(:rate, name: :rate_must_be_greater_than_zero)
|> calculate_total()
end
defp calculate_total(
%Changeset{valid?: true, changes: %{word_count: word_count, rate: rate}} = changeset
) do
total = Decimal.mult(word_count, rate)
change(changeset, %{total: total})
end
end
- User migration:
defmodule Tradtrack.Repo.Migrations.CreateUsersTable do
use Ecto.Migration
def change do
create table :users do
add :name, :string
add :last_name, :string
add :email, :string
add :nickname, :string
add :password_hash, :string
timestamps()
end
# ensures fields email and nickname are unique
create unique_index(:users, [:email])
create unique_index(:users, [:nickname])
end
end
- User schema
defmodule Tradtrack.User do
use Ecto.Schema
import Ecto.Changeset
alias Ecto.Changeset
alias Tradtrack.Project
@primary_key {:id, :binary_id, autogenerate: true}
@required_params [:name, :last_name, :email, :nickname, :password]
@email_regex ~r/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/
schema "users" do
field :name, :string
field :last_name, :string
field :email, :string
field :nickname, :string
field :password, :string, virtual: true
field :password_hash, :string
has_many :projects, Project
timestamps()
end
def changeset(params) do
%__MODULE__{}
|> cast(params, @required_params)
|> validate_required(@required_params)
|> unique_constraint([:email])
|> unique_constraint([:nickname])
|> validate_format(:email, @email_regex)
|> validate_length(:password, min: 6)
|> put_password_hash()
end
defp put_password_hash(%Changeset{valid?: true, changes: %{password: password}} = changeset) do
change(changeset, Bcrypt.add_hash(password))
end
defp put_password_hash(changeset), do: changeset
end
Any help will be much appreciated!
Thank you very much!