leon_cruz

leon_cruz

How to validate Ecto Associations properly?

Hello there, I’m getting some problems in validate associations in Ecto. I have two schemas, Video and Playlist (a playlist can have many videos). The Video schema has a following changeset:

def changeset(video, attrs \\ %{}) do
  video
    |> cast(attrs, [:name, :url, :playlist_id])
    |> validate_required([:name, :url, :playlist_id])
end

And Playlist has the following changeset:

def changeset(playlist, attrs \\ %{}) do
  playlist
    |> cast(attrs, [:name, :category])
    |> cast_assoc(:videos, required: true)
    |> validate_required([:name, :category])
end

What I want is when create a video, a playlist must be required, and when create a playlist, at least, one video has to be informed. This works for the video case, but when I try to create a playlist, there is a validation error on playlist_id of video. Anyone has some idea how to proceed with this? What I’m doing wrong? Thanks!

Most Liked

soup

soup

Sounds like you’re trying to build everything in one go? But since the playlist doesn’t exist yet, it doesn’t have an id, so it can it be given to the videos, so they fail their validation.

Confession: I can’t remember the last time I used cast_assoc personally, I set association ids manually. I also don’t like how IIRC you have to always give all the relationships in total which can be cumbersome.

I would do something like this, where its a two step transaction. You can also try ecto.multi.

###
### Writing this on my way out the door, so take it as a suggestion
###

def create_playlist_with_videos(name, category, videos) do
  # I normally have my own wrapper around transaction, so trying to remember
  # exactly without docs.
  Repo.transaction(fn ->
    with {:ok, playlist} <- create_playlist(name, category),
         # now playlist exists with an id so we can create videso
         {:ok, videos} <- create_playlist_videos(playlist, videos) do
      # transaction() wraps result in {:ok, _}
      %{playlist | videos: videos}
    else
      # can write this nicer, ideally the {:error, _} could just fall
      # out and fail the transaction
      {:error, e} -> rollback(e)
    end
  end)
end

defp create_playlist(name, category) do
  %Playlist{}
  # its ok to have more than one changeset function, i find it useful to
  # have at least a create_ and update_ as often they want different
  # validations.
  |> Playlist.create_changeset(name, category)
  |> Repo.insert()
end

defp create_playlist_videos(%Playlist{} = p, videos) do
  videos
  |> Enum.map(fn v ->
    %Video{}
    |>Video.create_changeset(playlist.id, v.name, v.url)
    |> Repo.insert()
  end)
  |> Enum.reduce({:ok, []}, fn
    # acc is error, perpetuate
    # you can use reduce_while to quit out
    _, {:error, e} -> {:error, e}
    # vid was ok, save and keep going
    {:ok, vid}, {:ok, vids} -> {:ok, [vids | vid]}
    # vid was error, return error (or collate all errors or ...?)
    # how you handle these errors is kind of application specific
    {:error, cs} _ -> {:error, :idk_lol}
  end)
end

e: you also probably want to put a null: false constraint in the database too if you haven’t.

al2o3cr

al2o3cr

cast_assoc combined with validate_required on the child schema’s foreign key will not work (see also a lot of the search results on this forum for cast_assoc). When you’re creating a Playlist, the related Video changesets need to be valid before the Playlist has an ID.

My suggestion to resolve this:

  • use a changeset function in cast_assoc(:videos, ...) that doesn’t cast or require playlist_id; that value will be set by the Ecto machinery automatically

  • if a video cannot be created without a playlist_id, enforce this at the database level with a null: false constraint on the column

A rule I find useful to help decide if something should be validated in a changeset function: is there a meaningful way to give feedback about a resulting error to the user? For instance, a blank name or category can show a “can’t be blank” message next to the UI for that value - what feedback could a missing playlist_id give?

leon_cruz

leon_cruz

i’ve already setup this

For this case, the message error will be like: “must be select a playlist”

Where Next?

Popular in Questions Top

siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

Other popular topics Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29377 241
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

We're in Beta

About us Mission Statement