-
Notifications
You must be signed in to change notification settings - Fork 22.1k
Model error as object #32313
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
Model error as object #32313
Conversation
|
@lulalala is there a reason why you can't emulate the hash-based API? |
|
@pixeltrix for the hash like APIs, my thoughts are:
|
|
Everything that was removed needs to be deprecated first. We don't make breaking changes without deprecation, even in major versions.
Yes, we try very hard to make possible that an old Rails version can read data from the new version and also the opposite. This make possible for applications to slowly rollout new versions to production. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't use yarn, we use rdoc, so we need to follow the Rails documentation guideline.
Cool. I'll add those back then. How about semantic change such as
Got it, but I still have a question. When |
Usually we only think about backward compatibility between two close release. In that case 5.1 and 5.2. Right now it would be 6.0 and 5.2 |
Can we introduce a new method with the new semantic and keep the old method with the previous semantic but with deprecation? |
I realized there is a better solution. I check arity of the block passed to |
|
That is a good way. 👍 |
7bb3407 to
9223a11
Compare
|
I have added back existing methods, some with deprecation warning (free to discuss about which to deprecate). The The YAML and marshal dumps are also made compatible with past versions. I added tests for each. I am starting to look into other tests which broke, and I learned a lot about what interface are needed. For example I added I'll update in the future once all tests are fixed. After that I'll add doc and optimize :) |
c904732 to
3d9cc8c
Compare
|
I think I have fixed almost all tests (the remaining few seems to be sporadic). I discovered there are many cases of Another behavior change is that details will not auto populate with empty array if it is access with a missing key. If the deprecation proposal here is finalized, I'll start adding code so tests won't have deprecation warning all over the place. |
3d9cc8c to
504b22b
Compare
58a565f to
14510d4
Compare
14510d4 to
fe13e6c
Compare
|
Hi @tenderlove, we have met in Ruby Kaigi, and you mentioned that for emulated message arrays, I should freeze those so attempts to append to them would fail. This is now added. Cheers! (Sorry I forgot who was sitting next to you who said I can also ping him as well) |
72db2d6 to
8344612
Compare
|
Hi @Larochelle, I see that you made a few changes lately to errors.rb. I am slowly trying to add the change you made to here, but after some digging, I am feeling that maybe we can DRY the logic a bit, as |
8344612 to
5a1ec44
Compare
5a1ec44 to
4719754
Compare
With the switch to ActiveModel::Error in Rails 6.1, the hash-based access can be retired. Reference: rails/rails#32313
Nobody has an XML API that would return errors? Why need to use a separate gem? |
|
Because we don't want to maintain something that Rails itself doesn't promote. In the past, we used to promote XML APIs. Those are long gone. We still support you to implement a XML API if you want to, but we will not maintain code to make that happen. |
|
I don't understand what does it mean to support a use case without maintaining code to make that happen. Also not sure who that "we" is and whether any formal decisions were made to prevent somebody to re-introduce the Either way, I assume new projects supporting XML API are a very rare occasion but for sure a lot of apps exist that need to workaround simple things like this when upgrading Rails. In my opinion a respectable app would not be able to drop a whole API without loosing user base. And something so fundamental that was promoted in the past being removed from the framework.. idk, looks wrong to me. |
|
"We" is the Rails core team, and yes, formal decisions to prevent people to re-introduce When I say that "We still support" I mean that Rails still supports that. If you want to generate XML representation of a If you want support to |
Problem: Since rails/rails#32313, `ActiveModel::Errors` does not hold anymore a hash of errors: it introduced a new object `ActiveModel::Error` which `ActiveModel::Errors` maintains a list of. Later, `ActiveModel::Errors` was enriched with the `#errors` method that return the list of structured errors. As a result, calling `invalid!` with `ActiveModel::Error` will return the list of structured error and no more the good old hashes of errors. This is the case when the `request_object` is an invalid `ActiveModel`. Solution: To workaround this issue, we forbid the calls to `#errors` on `ActiveModel::Errors` objects.
Problem: Since rails/rails#32313, `ActiveModel::Errors` does not hold anymore a hash of errors: it introduced a new object `ActiveModel::Error` which `ActiveModel::Errors` maintains a list of. Later, `ActiveModel::Errors` was enriched with the `#errors` method that return the list of structured errors. As a result, calling `invalid!` with `ActiveModel::Error` will return the list of structured error and no more the good old hashes of errors. This is the case when the `request_object` is an invalid `ActiveModel`. Solution: To workaround this issue, we forbid the calls to `#errors` on `ActiveModel::Errors` objects.
Problem: Since rails/rails#32313, `ActiveModel::Errors` does not hold anymore a hash of errors: it introduced a new object `ActiveModel::Error` which `ActiveModel::Errors` maintains a list of. Later, `ActiveModel::Errors` was enriched with the `#errors` method that return the list of structured errors. As a result, calling `invalid!` with `ActiveModel::Error` will return the list of structured error and no more the good old hashes of errors. This is the case when the `request_object` is an invalid `ActiveModel`. Solution: To workaround this issue, we forbid the calls to `#errors` on `ActiveModel::Errors` objects.
Problem: Since rails/rails#32313, `ActiveModel::Errors` does not hold anymore a hash of errors: it introduced a new object `ActiveModel::Error` which `ActiveModel::Errors` maintains a list of. Later, `ActiveModel::Errors` was enriched with the `#errors` method that return the list of structured errors. As a result, calling `invalid!` with `ActiveModel::Error` will return the list of structured error and no more the good old hashes of errors. This is the case when the `request_object` is an invalid `ActiveModel`. Solution: To workaround this issue, we forbid the calls to `#errors` on `ActiveModel::Errors` objects.
Problem: Since rails/rails#32313, `ActiveModel::Errors` does not hold anymore a hash of errors: it introduced a new object `ActiveModel::Error` which `ActiveModel::Errors` maintains a list of. Later, `ActiveModel::Errors` was enriched with the `#errors` method that return the list of structured errors. As a result, calling `invalid!` with `ActiveModel::Error` will return the list of structured error and no more the good old hashes of errors. This is the case when the `request_object` is an invalid `ActiveModel`. Solution: To workaround this issue, we forbid the calls to `#errors` on `ActiveModel::Errors` objects.
Problem: Since rails/rails#32313, `ActiveModel::Errors` does not hold anymore a hash of errors: it introduced a new object `ActiveModel::Error` which `ActiveModel::Errors` maintains a list of. Later, `ActiveModel::Errors` was enriched with the `#errors` method that return the list of structured errors. As a result, calling `invalid!` with `ActiveModel::Error` will return the list of structured error and no more the good old hashes of errors. This is the case when the `request_object` is an invalid `ActiveModel`. Solution: To workaround this issue, we forbid the calls to `#errors` on `ActiveModel::Errors` objects.
Problem: Since rails/rails#32313, `ActiveModel::Errors` does not hold anymore a hash of errors: it introduced a new object `ActiveModel::Error` which `ActiveModel::Errors` maintains a list of. Later, `ActiveModel::Errors` was enriched with the `#errors` method that return the list of structured errors. As a result, calling `invalid!` with `ActiveModel::Error` will return the list of structured error and no more the good old hashes of errors. This is the case when the `request_object` is an invalid `ActiveModel`. Solution: To workaround this issue, we forbid the calls to `#errors` on `ActiveModel::Errors` objects.
Summary
Add
ActiveModel::Errorclass for encapsulating a single error. It handles message generation and details access.Utilize this in
Errorsclass, changing it from a hash based interface, to an array ofErrorobjects. Add more flexible query methods likewhere.Introduction
ActiveModel#errors interface is currently not very object oriented. For some complex use cases, this design made it a bit tedious to use. I feel these issues can be remedied by encapsulating each individual error as an object.
Last year I implemented AdequateErrors gem to do this, and it solved many of my problems. I later found out that back in 2016, @eprothro suggested the same idea too on the core mailinglist, and Rafael was positive about this. So I made this PR.
I've made it to respect Rails’ deprecation policy. As this contains breaking changes, can this go straight into Rails 6.0?
I will fix other specs/add doc/performance tuning once interface is finalized.
Benefits
More flexible query interface such as
whereIt’s easy to query one particular object using the
whereclause.delete,add,added?,whereall share the same method signature (attribute, type, options). So we are able to delete specific errors now:Testing is more precise and flexible
We can now test if “foo” error exists, regardless of its options hash.
In the past, added? works by re-render the message and compare it against current message. Therefore if level is not provided, it will return false. In the PR,
added?only compare Error's attributes against what's provided, so it can be more general or more specific depending on the needs.Get message of corresponding details of one particular error
If you saw that name attribute has two
foo_errorand onebar_error, e.g.:How do you back track the message on the third particular error? With the current implementation, we have to resort to using array indexes:
Or we can call
generate_messageto recreate a message from the details, but that's also tedious.With OO, we won't have this problem. Error is represented as an object, message and details are its attributes, so accessing those are straightforward:
Lazily evaluating message for internationalization
@morgoth mentioned this issue that when you're adding error, it's translated right away.
This PR addresses this by lazily evaluating messages only when
messageis called.Opens possibility to advanced modding
Once errors are objects, it’s easy to add functionality on top of them. We can have custom methods to disable global attribute prefix on error’s full messages.
List of API changes
[]unchanged, deprecated (use
messages_forinstead)as_json,blank?,clear,count,empty?unchanged
addunchanged
added?mostly unchanged, for the one change see "Testing is more precise and flexible"
deleteextended, so we can give more specific condition such as error type or options.
eachIf we use
each{|attr,msgs|}then it behaves the same as before. DeprecatedIf we use
each{|error|}then it loops through Error array.full_messagedeprecated as it is no longer needed.
unchanged
message is generated in Error.full_messages,full_messages_forunchanged
messages_fornew, to replace deprecated
[]wherenew, query for error objects
generate_messagedeprecated
Part of Error as message is generated there.has_key?key?,keysdeprecated
include?,size,to_hash,to_xml,to_aunchanged
valuesdeprecated
importnew, imports one error as a nested error. Useful in Form Object and nested attribute error importing.
messages,detailsunchanged
Some questions I have
I am not sure what's the policy for marshal across versions.marshal_dumpandmarshal_loadare implemented, but do they have to support marshaling across Rails versions?This has been done.
Can we deprecate
to_xml? I bet is rarely used, and can exists as a gem.