Confusion about cast_assoc and cast FK

Hi all,

It’s been brought up several times that casting foreign keys is a bad practice.
However, if you want cast_assoc to work properly, you have to have the parent id listed in the child cast fields, otherwise the child associations don’t get their parent id. Am I correct about that?

Just wondering if everything was correct, and if there are other better ways of handling it.

Thanks!

1 Like

stupid question (maybe I do need more coffee): what is “fk”?

1 Like

Foreign Key. Thats the value in a relational database, that is used as key in a different table to wire up relationship.

1 Like

sure, I know what a foreign key is. The acronym wasn’t obvious for me, however, possibly worth updating in original post.

1 Like

Updated @hubertlepicki
I thought it’d be pretty clear in Ecto/DB context what “PK” and “FK” mean.

2 Likes

Still looking for help… Could anyone clarify this?

1 Like

To be honest, I never use cast_assoc as I almost never have the complete other record to link with, I just assign the fk_id straight. :slight_smile:

2 Likes

Can you post some code of how you’re currently doing it? I believe cast_assoc should be called from the parent changeset, which means it does have the parent ID and will be able to assign it to the child. For instance,

%{child_data: %{some: data}} = changes
def changset_fun_of_parent(parent, changes) do
  parent
  |> cast(params, [ ])
  |> cast_assoc(:child)
end
2 Likes

Say we have Post, Comment and User

a Post has many comments, it obviously belongs to a User but not important here
a Comment has some text body, belongs to a Post and belongs to a User

Let’s say I follow the “no cast fk” rule
I don’t cast either post_id or user_id in Comment's changeset function
And I have cast_assoc(:comments) in Post's changeset function

When I create a Post with changeset(%Post{user_id: ...}, %{"comments" => [%{"body" => "...", "user_id" => user_id}]}

I would get some error like this for the comment we are trying to create alongside the post:
errors: [post_id: {"can't be blank", [validation: :required]}, user_id: {"can't be blank", [validation: :required]}]
The user_id part I can understand, I probably have to cast_assoc(:user) on Comment as well.
The post_id part I don’t understand, does cast_assoc not pass parent id properly via the Struct but via the params?
What’s the proper way of doing this?

2 Likes

I just got the same problem: cast_assoc doesn’t seem to set parent’s FK on newly built child. Have you figured it out?

TL;DR - if the foreign key field is meant to be set programatically, then don’t add a validation.

I don’t have enough context to answer for sure but it is worth pointing out that data in Ecto is validated upfront on all entities. So if you are expecting user_id to be set and that is set through an association, then the validation will never pass.

However, if user_id is meant to be set programatically, then it should not have a validation in the first place. A validation is meant to be reported to users of your application. If the user gets a “post_id is blank” error and the field is set programatically, how can they even fix the error?

In those cases, it is best to make sure the operation fails altogether (by using DB constraints for example), so you detect such bugs early.

8 Likes

Lol I just got hit by this again and found my own question.

Note to future myself and others interested: validate_required is meant for user submitted fields.

Thanks Jose!

4 Likes

In those cases, it is best to make sure the operation fails altogether (by using DB constraints for example), so you detect such bugs early.

One might say that to “make sure the operation fails altogether”, you might perform some kind of validation, so you don’t have to go to the database to get an error message. :laughing:

But I’m mainly just here to put a +1 on finding it confusing.

1 Like