Skip to content
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 diff for many associations without primary keys #144

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

ddidwyll
Copy link

Hi, I actively use ecto_diff in my projects, and sometimes I miss the ability to diff on embeds_many associations without a primary key.
I propose a patch to add this feature.

@sourcelevel-bot
Copy link

Hello, @ddidwyll! This is your first Pull Request that will be reviewed by SourceLevel, an automatic Code Review service. It will leave comments on this diff with potential issues and style violations found in the code as you push new commits. You can also see all the issues found on this Pull Request on its review page. Please check our documentation for more information.

lib/ecto_diff.ex Outdated Show resolved Hide resolved
@vanvoljg
Copy link
Contributor

Hello! Thank you for the PR! 🎉

It would be fantastic if you could include a test for this feature. Is that possible?

@ddidwyll
Copy link
Author

Yes, of course, I'll write tests a little later.

@ddidwyll
Copy link
Author

I wrote a small test that shows how this feature can work.

@vanvoljg
Copy link
Contributor

Hello! Sorry about the delay in getting back to you. Too many GitHub notifications. I'll take a look at these changes shortly!

Comment on lines -326 to +317
prev_child = Map.get(previous_map, key)
current_child = Map.get(current_map, key)
if primary_key_fields == [] do
from = max(length(previous), length(current)) - 1

do_diff(prev_child, current_child)
end)
|> Enum.reject(&no_changes?/1)
from
|> Range.new(0)
|> Enum.map(fn i ->
prev_child = Enum.at(previous, i)
current_child = Enum.at(current, i)

if diffs == [] do
acc
else
[{field, diffs} | acc]
do_diff(prev_child, current_child)
end)
Copy link
Collaborator

@doughsay doughsay Feb 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The performance of this isn't great, because Enum.at traverses the whole list each time. It would be better to zip the two lists together first and traverse the zipped list instead. The problem of course is that Enum.zip stops at the shortest list. Here's a simple implementation of a zip that zips all the way to the longest list, defaulting missing elements to nil: https://gist.github.com/nathan-cruz77/586092fa2487da2bdd5603934e7db4ad

I would recommend adding this util function somewhere and using it here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good idea

Copy link
Contributor

@vanvoljg vanvoljg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, and thank you for the contribution.

I'd like to support doughsay's suggestion and make a couple comments for how to update your PR with the changes he recommended.

Comment on lines +308 to +317
from = max(length(previous), length(current)) - 1

do_diff(prev_child, current_child)
end)
|> Enum.reject(&no_changes?/1)
from
|> Range.new(0)
|> Enum.map(fn i ->
prev_child = Enum.at(previous, i)
current_child = Enum.at(current, i)

if diffs == [] do
acc
else
[{field, diffs} | acc]
do_diff(prev_child, current_child)
end)
Copy link
Contributor

@vanvoljg vanvoljg Feb 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along with doughsay's suggestion, once you bring in that zip_longest function, this implementation becomes:

zip_longest(previous, current)
|> Enum.map(&do_diff(&1, &2))

end)

keys
|> Enum.reverse()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, once you make the above change, you'll need to pull this reverse back out to the end of this whole function body, in the pipeline after Enum.reject/2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants