-
-
Notifications
You must be signed in to change notification settings - Fork 407
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
Deprecation of copy and Copyable #322
Merged
Merged
Changes from 2 commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
- 2018-03-24 | ||
- RFC PR: 322 | ||
- Ember Issue: (leave this empty) | ||
|
||
# Deprecation of Ember.copy and Ember.Copyable | ||
|
||
## Summary | ||
|
||
This RFC recommends the deprecation and eventual removal of `Ember.copy` and the `Ember.Copyable` mixin. | ||
|
||
## Motivation | ||
|
||
A deep-copy mechanism is certainly useful, but it is a general JavaScript problem. Ember itself doesn't need to offer one, especially one that Ember itself isn't using internally. This function and its accompanying mixin arrived with SproutCore, a long time ago, and are not used by Ember itself, even though they currently reside in `@ember/object/internals`. | ||
|
||
`ember-data` uses `Ember.copy` to do deep-copies. However, the `ember-data` team finds its needs would be better served by a private deep-copy mechanism that doesn't flow inadvertently through external interfaces into the `Ember.copy` methods of user-supplied objects. These interfaces are not designed to support deep copies of user-supplied data, and it can raise havoc in the form of hard-to-diagnose bugs, especially in test scenarios. | ||
|
||
Since `ember` and `ember-data` do not intend to use this mechanism going forward, it would be better to remove it from the Ember codebase and extract it into an add-on for those who wish to continue to use it. | ||
|
||
## Detailed design | ||
|
||
There are four steps to deprecating any function: | ||
* logging the deprecation in the call | ||
* removal of calls to the function from ember and any add-ons that ship with ember-cli | ||
* extraction to an add-on | ||
* eventual removal of the feature in the stated release (in this case 4.0.0). | ||
|
||
This RFC deprecates the `copy` function and `Copyable` mixin of `@ember/object/internals`. | ||
|
||
Shallow copies of the form `copy(x)` or `copy(x, false)` can be replaced mechanically with `Object.assign({}, x)`. The simplest way to deal with deep copies in any situation depends upon the nature of the data involved. | ||
|
||
### Current internal uses | ||
|
||
#### `ember-source` | ||
|
||
This following modules in `packages/ember-runtime/lib` implement the code being deprecated: | ||
|
||
* `copy.js` contains the `copy()` function that will log the deprecation before executing, | ||
* `mixins/copyable.js` provides the `Copyable` mixin, but it contains no executable code to deprecate. | ||
* `mixins/array.js` - The `NativeArray` mixin extends the `Copyable` mixin and implements `copy()`. | ||
|
||
The following tests in `packages/ember-runtime/tests` use the implementation above: | ||
|
||
* `core/copy_test.js` tests the `copy()` method itself. | ||
* `copyable-array/copy-test.js` tests the `copy()` method of a `NativeArray` for identical results. | ||
* `helpers/array.js` provides the arrays used by the `NativeArray` test above. | ||
* `system/native_array/copyable_suite_test.js` tests the independence of the results of deep copying a `NativeArray` | ||
|
||
The route `packages/ember-routing/lib/system/route.js` has one shallow copy, but the test `packages/ember/tests/routing/decoupled_basic_test` is using deep copy. | ||
|
||
The `copy()` methods in `packages/ember-metal/lib/map.js` and `chains.js` and their use in `meta.js`, and `map_test.js` are unrelated. | ||
|
||
During the deprecation period, the `Ember.copy` method and the `NativeArray.copy` methods will carry a deprecation warning. At the end of this period, we will remove the deprecated elements: | ||
* The deprecated copy() method and the Copyable mixin | ||
* The use of Copyable in NativeArray | ||
* The deprecated copy() method in NativeArray | ||
|
||
Seamlessly weaning `NativeArray` off of `Copyable` could potentially be a little tricky, especially if we continue delegating to it during the deprecation period, but I think I see a potential way through. | ||
|
||
At present, the `Ember.copy` method already can copy JS arrays of any kind without delegating to `NativeArray`. If `Ember.copy` itself is passed a NativeArray (which implements `Copyable`) it will delegate to `NativeArray.copy`. If `Ember.copy` is passed an object containing a `NativeArray` as a property, when it gets to that property, it will copy it like any other array using the generic copying mechanism - without even considering that it is `Copyable`. The behavior is inconsistent, but I'm not sure if there is a deliberate reason for this. | ||
|
||
If we change `Ember.copy` to consistently always use the array copy mechanism to copy arrays, even arrays that implement `Copyable`, the `NativeArray.copy` method should never be called as a side-effect of calling `Ember.copy`. By deprecating `NativeArray.copy` as well, the only users who ever see this deprecation will be those who are calling it directly. | ||
|
||
Even if we don't make this change in `Ember.copy`, we absolutely must do so in the version of `copy` in the add-on. We must also exempt arrays from the restriction on `EmberObject` and `Copyable`, since we will be removing `Copyable` from `NativeArray`. | ||
|
||
Those using the add-on would need to mechanically adjust any uses of `myArray.copy(deep)` to `copy(myArray, deep)` in order to avoid the deprecation message. | ||
|
||
I cannot see a way to notify the user through deprecation at runtime about their own objects that are extending `Copyable` and contain `copy()` methods that will no longer be called when the deprecated `Ember.copy()` method goes away. Our best bet for this might be to supply a new eslint warning that flags the import of `Copyable`. | ||
|
||
#### `ember-data` | ||
|
||
The following code in `ember-data` uses `copy()`, but only for shallow copies: | ||
* `addon/-private/system/model/internal-model.js` - one use | ||
* `addon/-private/system/snapshot.js` - two uses | ||
* `addon/-private/system/store.js` - one use | ||
|
||
All of the following uses in tests perform deep copies: | ||
* `tests/integration/adapter/build-url-mixin-test.js` - two uses | ||
* `tests/integration/adapter/rest-adapter-test.js` - two uses | ||
* `tests/integration/store-test.js` - two uses | ||
* `tests/unit/system/relationships/polymorphic-relationship-payloads-test.js` - four uses | ||
|
||
The `copy()` methods referenced in `addon/-private/system.map.js` and `addon/-private/system/relationships/state/relationship.js` are unrelated. | ||
|
||
It would appear that deep copy is used within these packages only during testing, and generally to ensure fresh test data without side-effects. | ||
|
||
### Current external uses | ||
|
||
The key considerations for add-ons or apps looking for an alternative to copy() and Copyable are: | ||
* Do they call `copy()` to do shallow copies or deep copies? | ||
* If deep copies are being performed, are the objects involved POJOs or are they derived from `EmberObject`? | ||
* Do they provide objects that use the `Copyable` mixin with `copy()` methods intended for use in deep copies by other classes? | ||
* Is the data you are copying the sort of thing where you can do the copy in its behalf, or does it require collaboration from the object itself? Or are the contents so open-ended that you can't possibly know? | ||
|
||
Shallow copies are directly supported by ES6. It's easy to perform recursive deep copies for most simple POJOs without delegating work to the object you are copying. For more complex data, you may need some kind of recursive delegation. `Copyable` is a delegation mechanism, and apps and add-ons that require delegation will probably want to use the proposed add-on. | ||
|
||
The Code Search capabilities of emberobserver are a wonderful way to get a glimpse of how code in the wild is using particular features. | ||
|
||
A quick search of the top-scoring add-on packages revealed that most, but by no means all, of the uses of `copy()` in the modules were for shallow copies that can be accomplished using Object.assign, so a lot of the code affected by this deprecation can rely on a simple substitution. | ||
|
||
Very few packages used `Copyable` - only 9 across the whole set - and most used the feature for only one class. `ember-data-copyable` is probably most wedded to the mechanism: it delivers a tasking mixin for `Copyable`. `ember-data-model-fragments` has pretty open-ended properties. These add-ons would be likely to use the proposed add-on moving forward. `ember-restless`, and `ember-calendar` appear more bounded. Any deep copy mechanism for POJOs may meet their needs. | ||
|
||
### Add-on | ||
|
||
The add-on will supply the `copy()` function and the `Copyable` mixin based on the existing code, modified as indicated above for handling of arrays. | ||
|
||
We could treat the add-on as the extraction of a feature from the monolithic `ember-source`, as was recently done for strings. If we choose to frame it in that way, the naming should follow the conventions set out for extracting elements of Ember into their own packages. If we choose not to frame it that way, then naming is one of the things this section should specify clearly. | ||
|
||
## How we teach this | ||
|
||
### Communication of change | ||
|
||
We need to inform users that `Ember.copy` and `Ember.Copyable` will be dprecated and in what release it will occur. This notification should also point them to the add-on for those who need it. | ||
|
||
### Official code bases and documentation | ||
|
||
We do not actively teach the use of `Ember.copy`. [True? Not true?] We will need to remove any passing references to `Ember.copy` from the Ember guides, from the Super Rentals tutorial, and anywhere it appears on the website. | ||
|
||
Once it is gone from the code, we also need to verify it no longer appears in the API listings. | ||
|
||
We must provide an entry in the deprecation guide for this change: | ||
* describing the use of `to = Object.assign({},from)` for shallow copies. | ||
* pointing out viable alternatives for deep copies. | ||
* directing heavy users of deep copies to the addon. | ||
|
||
## Drawbacks | ||
|
||
The primary drawback is the API churn of people pulling it out of their code. However, for most uses, the change will be straightforward, and the add-on will be available for the foreseeable future for those who want to continue with the implementation. | ||
|
||
## Alternatives | ||
|
||
We could simply leave it in place as a utility for others to use. Even then, it would make sense to split it out into its own module, as has already been done for strings, so the work would be much the same. | ||
|
||
## Unresolved questions | ||
|
||
None at the moment... | ||
|
||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 can implement a deprecation at “extend” time such that anything including the copyable mixin received the deprecation (even if copy is never called on it), happy to deep dive the details with you in slack...
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.
That would be welcome - but that would include all arrays, no?
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.
"at extend time" - Since
extend
is defined insystem.core-object
, also inember-runtime
, implementing a deprecation at that level might be architecturally sound. The check would be a small tax on every extend, though, which seems severe. @rwjblue Was that where you intended to put it? Or somewhere else in the chain of activity "at extend time"?