gmc
Inserting model with association using create functions
I’m trying to insert 2 associated models into the repo using the create_xxx functions generated by “mix ecto.gen.json”. I haven’t found a way to do this without getting various errors and I couldn’t find a post on this anywhere.
I have two models: Menu and MenuItem where MenuItem has a FK to store the associated Menu.
The following works without any issues, both records are created in the database:
menu = %Cms.Content.Menu{name: "Test"}
menu_item = %Cms.Content.MenuItem{name: "Test Item", menu: menu}
Cms.Repo.insert!(menu_item)
But when I try to use the create_xxx functions I get an error:
menu = Cms.Content.create_menu(%{name: "Test Menu"}) # works fine
menu_item = Cms.Content.create_menu_item(%{name: "Test Item", menu: menu}). # error
[debug] QUERY ERROR db=6.5ms
INSERT INTO “menus” (“inserted_at”,“updated_at”) VALUES ($1,$2) RETURNING “id” [{{2018, 2, 28}, {10, 58, 43, 347401}}, {{2018, 2, 28}, {10, 58, 43, 347411}}]
[debug] QUERY OK db=0.3ms
rollback
** (Postgrex.Error) ERROR 23502 (not_null_violation): null value in column “name” violates not-null constraint
table: menus
column: name
Why does Ecto try to insert the already created menu again? What is wrong here?
Here are the model definitions:
defmodule Cms.Content.Menu do
use Ecto.Schema
import Ecto.Changeset
alias Cms.Content.{Menu, MenuItem}
schema "menus" do
field :name, :string
has_many :menu_items, MenuItem
timestamps()
end
@doc false
def changeset(%Menu{} = menu, attrs) do
menu
|> cast(attrs, [:name])
|> validate_required([:name])
|> unique_constraint(:name, message: "Name is already taken.")
end
end
defmodule Cms.Content.MenuItem do
use Ecto.Schema
import Ecto.Changeset
alias Cms.Content.{Menu, MenuItem}
schema "menu_items" do
field :name, :string
belongs_to :menu, Menu
timestamps()
end
@doc false
def changeset(%MenuItem{} = menu_item, attrs) do
menu_item
|> cast(attrs, [:name])
|> put_assoc(:menu, [attrs.menu])
|> validate_required([:name, :menu])
|> unique_constraint(:name, message: "Name is already taken.")
|> unique_constraint(:order, message: "Order number is already taken.")
end
end
Marked As Solved
idi527
menu = Cms.Content.create_menu(%{name: "Test Menu"})
What’s returned from Cms.Content.create_menu/1? Is it {:ok, menu}?
I would probably avoid casting foreign keys in changesets (can open you up for vulnerabilities if attrs come from user input), but pass them to the create function “manually”.
@spec create_menu_item(map, for: %Menu{}) :: {:ok, %MenuItem{}} | {:error, Ecto.Changeset.t()}
def create_menu_item(attrs, for: %Menu{id: menu_id}) do
%MenuItem{menu_id: menu_id}
|> MenuItem.changeset(attrs)
|> Repo.insert()
end
# for menu item
def changeset(%MenuItem{} = menu_item, attrs) do
menu_item
|> cast(attrs, [:name])
# |> put_assoc(:menu, [attrs.menu])
|> validate_required([:name, :menu])
|> unique_constraint(:name, message: "Name is already taken.")
|> unique_constraint(:order, message: "Order number is already taken.")
end
Usage
{:ok, %Menu{} = menu} = create_menu(%{name: "Test Menu"})
{:ok, %MenuItem{} = menu_item} = create_menu_item(%{name: "Test Item"}, for: menu)
Also Liked
idi527
Just a keyword list.
idi527
Please use ``` to format your code blocks.
```
code
```
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #javascript
- #code-sync
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








