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

Error details and merging/composing #401

Closed
ngan opened this issue Nov 14, 2016 · 9 comments
Closed

Error details and merging/composing #401

ngan opened this issue Nov 14, 2016 · 9 comments
Labels

Comments

@ngan
Copy link
Contributor

ngan commented Nov 14, 2016

For specifics reasons, which I will explain later, I need to propogate the error details up the run chain. Currently when AI merge errors, it merges the details from the composed AI (or errors.merge! call) as translated messages. But I'm wonder if it would be possible to keep track of all the merged details by their base?

So, today, you have attribute, and error/message.
Perhaps we can keep a hash, called composed_errors or something that looks like this:

{
  Payment => ActiveModel::Errors,
  PaymentCharge => ActiveModel::Errors,
  Invoice => ActiveModel::Errors
}

When you merge, it will do as it does today and merge translated messages into the Errors object on the current AI. It however will also save the original (or dup'd) Errors class. If the keys conflict, it will merge the two ActiveModel::Errors together (since it's the same base). I think we can just modify the AI::Errors class to contain another hash inside to keep track of the original Errors.

The reason why I need this is because I use compose/errors.merge! a lot and at the topmost interaction, I need the error detail keys to print specific errors for the top most interaction (or rather, from the perspective of the topmost interaction). For example, let's say I have a Payment model. If the associated user on that Payment doesn't have enough money, then the error added would be: errors.add(:user, :not_enough_money). This is great at the payment level, but when you're 3 levels up, you don't want User does not have enough money as the error message, you want You do not have enough money. And the only I can see doing this is keeping the original error details, such that when I do the translate at the topmost level (topmost interaction), the translate key can be something like: en.activeinteraction.invoice.payment.not_enough_money.

Let me know your guys' thoughts. I can work on the PR.

@ngan
Copy link
Contributor Author

ngan commented Nov 14, 2016

Also, I believe this can help the error merging caveat:

When a composed interaction fails, its errors are merged onto the caller. This generally produces good error messages, but there are a few cases to look out for.

For those who care to handle it themselves.

@ngan
Copy link
Contributor Author

ngan commented Nov 14, 2016

outcome = CreateInvoice.run(....)
outcome.composed_errors.each do |base, errors|
  # I now have access to the base as well as all of its original error details. I can then use these two to build the translation key myself.
end

@tfausak
Copy link
Collaborator

tfausak commented Nov 14, 2016

This has some things in common with #245. I hesitate to make compose more complicated. For these complex interactions, would it make sense to use run directly instead of compose?

@ngan
Copy link
Contributor Author

ngan commented Nov 14, 2016

The problem isn't really with compose, it's more with errors.merge!--which compose happens to call. We do call run in some cases, but we still have to do errors.merge!, and it's there that you lose the base/details.

@ngan
Copy link
Contributor Author

ngan commented Nov 14, 2016

What I'm proposing is simply keeping track of the original Errors objects as we compose/merge! errors.

@AaronLasseigne
Copy link
Owner

Our current error merging does have a pretty unfortunate caveat. It seems like we need a way to map errors when merging/composing. Depending on an entire history of errors seems like it would be brittle. If someone changed how an interaction layers down works we wouldn't want it to break everything above it.

@ngan
Copy link
Contributor Author

ngan commented Nov 14, 2016

I think I might not be explaining things clearly... because what I'm proposing really has no impact on hows things work today. errors.merge! would continue to function the way it does. What I'm suggesting is an additional composed_errors accessor along side errors on interactions. When someone calls errors.merge! (or if it's called through compose), then we simply append to the composed_errors hash. This has would simply be a mapping of base objects to their original Errors object.

For example, let's say we have 3 layers of interactions, the composed_errors method on the topmost interaction would return the following:

{
  InteractionAction1 => ActiveModel::Errors,
  InteractionAction2 => ActiveModel::Errors,
  InteractionAction3 => ActiveModel::Errors,
}

The composed_errors hash could also contain errors from AR models...

{
  User => ActiveModel::Errors,
  InteractionAction1 => ActiveModel::Errors,
  InteractionAction2 => ActiveModel::Errors,
  InteractionAction3 => ActiveModel::Errors,
}

If you merge Errors and its base object already exists on the composed_errors hash, it would be merged into that Errors object.

@AaronLasseigne
Copy link
Owner

I get that it's new functionality. I'm not convinced knowing the inner details of the call chain will produce a good result. What if the 3rd interaction that's nested in the 2nd interaction gets swapped for some new interaction. The same error is returned but the field is named something else now. Suddenly your top level interaction is broken because it knows too much about the ones below it.

If we let each level understand and map errors from the calls it makes then you'll never have to worry about breaking more than one level from the composed interaction.

@ngan
Copy link
Contributor Author

ngan commented Nov 15, 2016

I understand your concerns. I think I'll solve my problem another way. Thanks for the feedback!

@ngan ngan closed this as completed Nov 15, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants