-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new functions to Repo behaviour #4427
base: master
Are you sure you want to change the base?
Conversation
Hi @tmbb! I need to understand the problem a bit better. Why can't the associations be loaded before you put the data in the changeset? Can you provide a general example with before/after that could benefit for this? |
@josevalim the problem of the "non-preloaded" associations happens when I use the An example can be found in the test I've added in the newest commit: ecto/test/ecto/repo/preload_test.exs Lines 65 to 105 in f46ed7f
|
Why can't you preload the associations into the struct before you call |
It looks like their issue is when the data is not already in the db but when casting will produce a changeset for the creation of a new entry. But I am still not 100% sure I understand why it's an issue. It seems like it's normal for that to exist only in |
As shown in the code above, I am already preloading the data before the cast. It still doesn't give me preloaded associations for the
The problem is not exactly that, the problem is that I want the I need the associations to be preloaded in the changeset data (the changeset data, that is, order to be able to iterate over the chapters in nested inputs in a Phoenix form (with I want the form to work even with changeset that haven't been persisted, in which the user mau be performing edits with incremental validation. I hope I've made it more clear |
I think the simplest solution is to add a flag to cast_assoc that resets the association if unloaded? |
i think something like that makes sense. to me it sounds like when the cast functions are creating changesets for new inserts the associations are defaulting to unloaded and that is causing the issue. |
Exactly, that's the issue. But I'm not sure that |
We should not really preload it. The issue you describe is for new entries, so we should allow resetting it for new entries, and then we will be all good. |
@tmbb I think it can be as simple as adding this change here data = if opts[:force_load], do: %{data | key => Relation.load!(data, original)}, else: data
changeset = %{force_update(changeset, opts) | data: data, changes: changes, valid?: valid?} By the time you get here you know if it's not loaded then it's a new entry. Otherwise this would have raised. I don't know if that's the best name for the opt but hopefully this gives the idea. |
@greg-rychlewski I didn't check the code (sorry, I am being lazy) but would that only be invoked for new records? For updates it can lead to weird failures. |
I think so but maybe I need to check all the cases more closely. My thinking is that original = Map.get(data, key) So when you do Relation.load!(data, original) The only thing it can do is change is an unloaded assoc to empty. And I don't think it should happen for updates because we do this at the beginning original = Map.get(data, key)
current = Relation.load!(data, original) Which raises if |
This PR is not suitable for merging right away because of a few reasons (detailed below), but it serves as a template for what I think should be included in Ecto.
Rationale
Currently there is no way to recursively preload associations in deeply nested changesets. Preloading association in deeply nested changesets is important if one is working with nested forms inside LiveView. LiveView expects forms built from changesets (not ecto structs!) and it expects associations to be preloaded so that the HTML components can iterate over the value's "children".
One could think of dropping changesets from LiveView, but changesets are really convenient to validate data and to make sure that errors are rendered correctly in HTML forms.
Nothing inside this PR refers to LiveView, of course, but it's just the motivating example that led me to write it.
Changes implemented in this PR
This PR implements two new public callbacks in the
Repo
behaviour, namely:@spec preload_in_result({:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}, list(), list()) :: {:ok, struct} | {:error, changeset}
which is optimized for piping results returned by functions such asRepo.insert()
andRepo.update()
@spec preload_in_changeset(changeset, list(), list())
to preload associations inside a changeset (the function above calls this one when given a{:error, changeset}
tuple)The implementation is not very efficient in the sense that it probably generates more queries than is really required, but that can be optimized by walking the nested changesets twice (once to get the IDs of all the parents to get the children in a single query and then to attribute each child to the correct parent based on the ID; changesets without IDs can't have children already in the database, and don't need to be queryeed - I already do this optimization).
Testing
I'm trying to test this with the
TestRepo
which uses a custom adapter (which I don't understand at all, I've never looked into adapter code), but it seems like theTestRepo
is not generating IDs correctly when multiple changesets are inserted (?).Questions