Search code examples
validationelixirecto

ecto model parameter not validated?


Might be that I'm missing something, but it feels that this should have been an invalid record:

pry(1)> project_params
%{"name" => ""}
pry(2)> changeset
%Ecto.Changeset{action: nil, changes: %{name: ""}, constraints: [], errors: [],
 filters: %{},
 model: %Elix.Project{__meta__: #Ecto.Schema.Metadata<:built>, id: nil,
  inserted_at: nil, name: nil, updated_at: nil,
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  user_id: 2}, optional: [], opts: [], params: %{"name" => ""}, prepare: [],
 repo: nil, required: [:name, :user_id],
 types: %{id: :id, inserted_at: Ecto.DateTime, name: :string,
   updated_at: Ecto.DateTime, user_id: :id}, valid?: true, validations: []}
pry(3)> changeset.valid?
true

Here's how I have the models defined:

Project

defmodule Elix.Project do
  use Elix.Web, :model

  schema "projects" do
    field :name, :string
    belongs_to :user, Elix.User

    timestamps
  end

  @required_fields ~w(name user_id)
  @optional_fields ~w()

  @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(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
  end
end

User

defmodule Elix.User do
  use Elix.Web, :model

  schema "users" do
    field :email, :string
    field :password, :string, virtual: true
    field :crypted_password, :string
    has_many :projects, Elix.Project

    timestamps
  end

  @required_fields ~w(email password)
  @optional_fields ~w()

  @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(model, params \\ :empty) do
    model
    |> cast(params, @required_fields, @optional_fields)
    |> unique_constraint(:email)
    |> validate_format(:email, ~r/@/)
    |> validate_length(:password, min: 5)
  end
end

and this is the code for the controller create action:

def create(conn, %{"project" => project_params}) do
    changeset =
      Ecto.build_assoc(conn.assigns.current_user, :projects) |>
      Project.changeset(project_params)
IEx.pry
    case Repo.insert(changeset) do
      {:ok, project} ->
        conn
        |> put_flash(:info, "Project #{project.name} created succesfully")
        |> redirect(to: project_path(conn, :index))
      {:error, changeset} ->
        render(conn, "new.html", project: changeset)
    end
  end

I am submitting the form without entering anything on purpose, so that I might test form display errors. What am I missing here?


Solution

  • The changeset.valid? will only be false if the value of name is nil. Since the value of name in your case is an empty string, the changeset will be valid.

    The best place to deal with forms passing empty strings as values is in the controller by adding the scrub_params plug in like so:

    plug :scrub_params, "project" when action in [:create, :update]

    Check the documentation for more info on scrub_params.