Cannot add constraint to changeset

Hey all,

I want to create a new action in order to write new posts in my project. Each post belongs_to a user an hence I have a :user_id field in my schema.

However, when I want to access the new template, I get the following error:

cannot add constraint to changeset because association 'user' does not exist. Did you mean one of ''?

My post_controller.ex:

defmodule GazetteWeb.PostController do
  use GazetteWeb, :controller

  alias Gazette.Repo
  alias Gazette.Post
  alias Gazette.User

  plug :scrub_params, "post" when action in [:create, :update] # convert blank_string params into nils

  def action(conn, _) do # helps to get three arguments in controller's actions
    apply(__MODULE__, action_name(conn),
      [conn, conn.params, conn.assigns.current_user]) # here are the three arguments
  end

[...]

  def new(conn, _params, current_user) do
    changeset =
    current_user
    |> Ecto.build_assoc(:posts)
    |> Post.changeset
    render conn, "new.html", changeset: changeset
  end

[…]

my post.ex:

defmodule Gazette.Post do
  use Ecto.Schema
  import Ecto.Changeset
  alias Gazette.Post


  schema "posts" do
    field :body, :string
    field :title, :string
    field :user_id, :id

    timestamps()
  end

  @required_fields ~w(title)a
  @optional_fields ~w(body)a

  @doc false
  def changeset(%Post{} = post, attrs \\ %{}) do
    post
    |> cast(attrs, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
    |> assoc_constraint(:user)
  end
end

my user.ex:

defmodule Gazette.User do
  use Ecto.Schema
  import Ecto.Changeset
  alias Gazette.User


  schema "users" do
    field :email, :string
    field :is_admin, :boolean, default: false
    field :is_writer, :boolean, default: false
    field :name, :string
    field :password, :string, virtual: true
    field :password_hash, :string

    has_many :posts, Gazette.Post

    timestamps()
  end

  @required_fields ~w(email name)a
  @optional_fields ~w(is_writer is_admin)a

  @doc false
  def changeset(%User{} = user, attrs \\ %{}) do
    user
    |> cast(attrs, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
  end

  def registration_changeset(struct, params) do
    struct
    |> changeset(params)
    |> cast(params, ~w(password)a, [])
    |> validate_length(:password, min: 6, max: 100)
    |> hash_password
  end

  defp hash_password(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true,
      changes: %{password: password}} ->
        put_change(changeset,
         :password_hash,
         Comeonin.Bcrypt.hashpwsalt(password))
        _ ->
          changeset
        end
      end
    end

Any idea ?

Hello there,

Right now, Ecto has no idea that a Post belongs_to a User, so you can’t use association constraints.

If you switch field :user_id, :id to belongs_to :user, Gazette.User in your Post schema you should be set. That’ll tell Ecto there is a field named ”user_id” and that it is also an association.

3 Likes

thank you ! That was the problem indeed.

But now I get a very bad error :frowning:

protocol Enumerable not implemented for %Gazette.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">, email: "corbin.julien@gmail.com", id: 1, inserted_at: ~N[2018-02-08 18:41:48.920024], is_admin: true, is_writer: false, name: "UjCorb", password: nil, password_hash: "$2b$12$XqVL4fXY787QjOedgib2yuWAIH8G3goL4pI8yhM0jp9Z9soxlkl7q", posts: #Ecto.Association.NotLoaded<association :posts is not loaded>, updated_at: ~N[2018-02-10 23:39:38.699793]}. This protocol is implemented for: DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, List, Map, MapSet, Postgrex.Stream, Range, Stream

The loaded user is myself.

Maybe should I drop/recreate/migrate the DB ?

EDIT: nevermind this is another problem; actually from my template.

1 Like

No, you don’t need to re-create the database. You’re trying to enumerate over a single struct instead of a list of structs, that’s all.

1 Like