Loading association data in Ecto changeset

Me and my group have been rewriting a large web app using Phoenix, and we came across a problem that we aren’t sure how to elegantly resolve. We’ve been trying to be more strict with our coding practices to keep side effects isolated and to keep the code clean.

Our issue is that one of our model’s changeset validations is dependent on an attribute of a child association. We should not be able to update the record if a boolean value is set to true in the child association.

However, we are not guaranteed that the changeset will always be called with a struct that preloads the association that we need. We want to avoid making a Repo.preload call in the model as much as possible because of separation of concerns, and we know this may set a precedent for poor coding practices in the future.

We have considered checking if the association is preloaded in the changeset, and throwing an error if it is not. However, we want to avoid this solution as much as possible.

Any opinions or ideas would be appreciated.

Sounds like this is a validation that should be performed on the SQL side via a constraint function or so?

1 Like

I agree with you that a constraint would be ideal. However, the association is in a different table so it isn’t possible to use a check constraint.

A trigger however.

A trigger would work well. I am not aware of any way of catching an exception raised by a trigger and converting this into a changeset error. Any ideas?

Sounds like a PR if not already supported? :wink:

I have a similar situation, and ended up preloading (with force: true) the association as the simplest solution.

My schema are tightly coupled and the check is on the child record, so separation of concerns wasn’t an issue.

@law Care to elaborate, please?

I need to preload an association in an order before being able to add a line item. I want to only use a changeset to accumulate changes to the order and eventually call Repo.update and I am not willing to make the compromise to do one SQL update on each of my chain-able functions.

However, right now I seriously have no idea how do I do both: (1) preload association(s) in an order and change the underlying changeset’s data field (which is an order) in the process and (2) only use the changeset to fetch the preloaded association(s) and work with them to be able to add a line item to the order.

The goal is:

  1. Chain-able functions that can preload associations and use them to append changes to changesets
  2. Being able to update the database explicitly, after we are done accumulating changes to an order.

Is that even possible with Ecto changesets?

@dimitarvp It sounds like you could use a ‘cast_assoc’ on the parent changeset. This would allow you to insert both the parent and child records together in one Repo call and similarly to update the child and the parent. ‘cast_assoc’ is essentially like casting an entire associations fields into the form of its parent.

It’s possible I’m not 100% understanding your question so in that case, my apologies.

@ideanl Not exactly. My newly created order – not yet persisted anywhere – has a client (a record that already exists inside the DB) and I now want to add a line item to it as well.

However, to add a line item, we need the client info. I think that cast_assoc is used when you are changing your association. I am not looking to change the client. I want to preload it inside the order’s changeset, while it’s still being appended to, because I cannot add a line item changeset without the preloaded client.

The search continues though… Or, sorry if I misunderstood you!

@dimitarvp Ah I think I understand. A solution I can think of is to use a context layer (in Phoenix 1.3 if you use Phoenix). The context layer would create the order or line items for an order while preloading the client itself. A context layer has actually fixed this problem for me by allowing me an extra layer to handle my business logic (including making some Repo calls) while hiding schemas in a lower level.