hey, Postgres is looking for a column category_id in articles tables and its not there, can you please post your migrations related to articles table.
I think you may you used a different column over category_id, if thats the case you need to specify the column.
I am not using any custom ids.
The current config should provide a category_id to the articles table but for some strange reason it doesn’t.
I think this may actually be a bug that happens if phoenix 1.4.11 receives to many errors and also I used mix ecto.reset.
Tomorrow i will try to create a post and categories in a new project only with relationships between them, too see if this happens again.
defmodule BlogApi.Repo.Migrations.CreateArticles do
use Ecto.Migration
def change do
create table(:articles) do
add :title, :string
add :content, :string
add :published, :boolean, default: false, null: false
add :category_id, references(:categories, on_delete: :delete_all)
timestamps()
end
end
end
My article schema
defmodule BlogApi.Blog.Article do
use Ecto.Schema
import Ecto.Changeset
alias BlogApi.Categories.Category
schema "articles" do
field :content, :string
field :published, :boolean, default: false
field :title, :string
belongs_to(:category, Category)
timestamps()
end
@doc false
def changeset(article, attrs) do
article
|> cast(attrs, [:title, :content, :published])
|> validate_required([:title, :content, :published])
|> unique_constraint(:title)
end
end
My schema for categories
defmodule BlogApi.Categories.Category do
use Ecto.Schema
import Ecto.Changeset
alias BlogApi.Blog.Article
schema "categories" do
field :name, :string
field :slug, :string
has_many(:articles, Article)
timestamps()
end
@doc false
def changeset(category, attrs) do
category
|> cast(attrs, [:name, :slug])
|> validate_required([:name, :slug])
|> unique_constraint(:name)
end
end
This line in article’s changeset specifies which fields will be casted for insertion. So I assume you set the category_id later in some context function with Ecto.Changeset.put_change before insertion. If not Postgresql will throws an error unless the category_id column can be null.
Also the problem may come from the submited form? You need to ensure user send a category id. Please show the part of the form where users fill or select a category.
Edit:
I think there are two ways to insert resource such as article that belongs to a parent (category in this case).
the category is already known before rendering the new article form. In that case generally the category id is present in the url. See nested resources. In this case you don’t cast the id in your changeset but you still need to set it before insertion.
the category id is provided directly from the submitted form. In this case you just need to cast as you do for the other fields. Ecto.Changeset.foreign_key_constraint can be used as validator to show appropriate error message when the id sent is not valid. Also in the form if you decide to use a select, you need to format the collection of categories to be a valid enum type. For example a list like [{value_1, id_1}, {value_2, id_2}, ... ]. Values are the options that users will read in the select, and the ids are what your changeste will cast.
After I set this Ecto.Changeset.foreign_key_constraint I get a more detailed error and I think it has something to do with how i used select in my form.
def create_article(attrs \\ %{}) do
%Article{}
|> Article.changeset(attrs)
|> Ecto.Changeset.cast_assoc(:category, with: &Category.changeset/2)
|> Repo.insert()
end
I think you want to that when you want users to create an article and a category at the same time or update an existing category at the same time. And you need to preload an empty struct or an existing record for the association following you to create a new or update an existing one.
But given this part of your form I can assume that the category to associate to the new article already exists and just has to be selected. In this case you just have to update your article schema with an additional changeste like below:
def create_changeset(article, attrs) do
article
|> cast(attrs, [:title, :content, :published, :category_id])
|> validate_required([:title, :content, :published, :category_id])
|> foreign_key_constraint(:category_id, message: "Category not found!")
|> unique_constraint(:title)
end
So I just add one more field in the cast and validate_required functions: category_id which will be submited by the form. The foreign_key_constraint validator will throw an error if by some mean the user attempt to submit an invalid category id. So you use this changeset only to create new article unless you want to allow user to change also the category when updating an article. If you don’t want them to be able to uppdate the category of existing articles you just use your initial changeset for article update.