Skip to content

Commit a5bbf98

Browse files
authored
Merge pull request rust-lang#369 from pzuraq/deprecate-computed-clobberability
Deprecate Computed Overridability and `.readOnly()`
2 parents 3388ada + 3f94ebf commit a5bbf98

File tree

1 file changed

+176
-0
lines changed

1 file changed

+176
-0
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
- Start Date: 2018-08-30
2+
- RFC PR: (leave this empty)
3+
- Ember Issue: (leave this empty)
4+
5+
# Summary
6+
7+
Deprecate computed overridability and `computed().readOnly()` in favor of
8+
read-only computeds as the default.
9+
10+
# Motivation
11+
12+
Computed properties have existed in Ember long before class syntax and native
13+
accessors (getters and setters) were readily available, and as such they have a
14+
few notable behavioral differences. As we move toward adopting native class
15+
syntax and using a decorator-based form of computeds, it makes sense to
16+
reconcile these differences so that users can expect them to work the same as
17+
their native counterparts.
18+
19+
The main and most notable difference this RFC seeks to deprecate is computed
20+
overridability (colloquially known as "clobbering"). There are some other
21+
notable differences, including the caching behavior of the `return` value of
22+
setter functions, which may be addressed in future RFCs.
23+
24+
## Overridability
25+
26+
When defining a native getter without a setter, attempting to set the value will
27+
throw a hard error (in strict mode):
28+
29+
```js
30+
function makeFoo() {
31+
'use strict';
32+
33+
class Foo {
34+
get bar() {
35+
return this._value;
36+
}
37+
}
38+
39+
let foo = new Foo();
40+
41+
foo.bar; // undefined
42+
foo.bar = 'baz'; // throws an error in strict mode
43+
}
44+
```
45+
46+
By constrast, computed properties without setters will be overridden when they
47+
are set, meaning the computed property is removed from the object and replaced
48+
with the set value:
49+
50+
```js
51+
const Foo = EmberObject.extend({
52+
bar: computed('_value', {
53+
get() {
54+
return this._value;
55+
},
56+
}),
57+
});
58+
59+
let foo = Foo.create();
60+
61+
foo.bar; // undefined
62+
foo.set('bar', 'baz'); // Overwrites the getter
63+
foo.bar; // 'baz'
64+
foo.set('_value', 123);
65+
foo.bar; // 'baz'
66+
```
67+
68+
This behavior is confusing to newcomers, and oftentimes unexpected. Common best
69+
practice is to opt-out of it by declaring the property as `readOnly`, which
70+
prevents this overridability.
71+
72+
# Transition Path
73+
74+
This RFC proposes that `readOnly` properties become the default, and that in
75+
order to override users must opt in by defining their own setters:
76+
77+
```js
78+
class Foo {
79+
get bar() {
80+
if (this._bar) {
81+
return this._bar;
82+
}
83+
84+
return this._value
85+
}
86+
87+
set bar(value) {
88+
this._bar = value
89+
}
90+
}
91+
```
92+
93+
## Macros
94+
95+
Most computed macros are overridable by default, the exception being `readOnly`.
96+
This RFC proposes that all computed macros with the exception of `reads` would
97+
become read only by default. The purpose of `reads` is to _be_ overridable, so
98+
its behavior would remain the same.
99+
100+
## Decorator Interop
101+
102+
It may be somewhat cumbersome to write overriding functionality or add proxy
103+
properties when overriding is needed. In an ideal world, computed properties
104+
would modify accessors transparently so that they could be composed with other
105+
decorators, such as an `@overridable` decorator:
106+
107+
```js
108+
class Foo {
109+
@overridable
110+
@computed('_value')
111+
get bar() {
112+
return this._value;
113+
}
114+
115+
@overridable
116+
@and('baz', 'qux')
117+
quux;
118+
}
119+
```
120+
121+
Currently this is not possible as computed properties store their getter/setter
122+
functions elsewhere and replace them with a proxy getter and the mandatory
123+
setter assertion, respectively. In the long term, making computeds more
124+
transparent in this way would be ideal, but it is out of scope for this RFC.
125+
126+
## Deprecation Timeline
127+
128+
This change will be a breaking change, which means we will not be able to change
129+
the behavior of `computed` until Ember v4.0.0. Additionally, users will likely
130+
want to continue using `.readOnly()` up until overriding has been fully removed
131+
to ensure they are using properties safely. With that in mind, the ordering of
132+
events should be:
133+
134+
1. Ember v3
135+
* Deprecate the default override-setter behavior immediately. This means that
136+
a deprecation warning will be thrown if a user attempts to set a
137+
non-`readOnly` property which does not have a setter. User's will still be
138+
able to declare a property is `readOnly` without a deprecation warning.
139+
* Add optional feature to change the deprecation to an assertion after the
140+
deprecation has been released, and to show a deprecation when using
141+
the `.readOnly()` modifier.
142+
* After the deprecation and optional feature have been available for a
143+
reasonable amount of time, enable the optional feature by default in new
144+
apps and addons. The main reason we want to delay this is to give _addons_
145+
a chance to address deprecations, since enabling this feature will affect
146+
both apps and the addons they consume.
147+
2. Ember v4
148+
* Remove the override-setter entirely, making non-overrideable properties the
149+
default.
150+
* Make the `readOnly` modifier a no-op, and show a deprecation warning when it
151+
is used.
152+
153+
The warnings should explain the deprecation, and recommend that users do not
154+
rely on setter behavior or opting-in to read only behavior.
155+
156+
# How We Teach This
157+
158+
In general, we can teach that computed properties are essentially cached native
159+
getters/setters (with a few more bells and whistles). Once we have official
160+
decorators in the framework, we can make this connection even more solid.
161+
162+
We should add notes on overridability, and we should scrub the guides of any
163+
examples that make use of overriding directly and indirectly via `.readOnly()`.
164+
165+
# Drawbacks
166+
167+
Overriding is not a completely uncommonly used feature, and developers who have
168+
become used to it may feel like it makes their code more complicated, especially
169+
without any easy way to opt back in.
170+
171+
# Alternatives
172+
173+
We could convert `.readOnly()` into `.overridable()`, forcing users to opt-in
174+
to overriding. Given the long timeline of this deprecation, it would likely be
175+
better to work on making getters/setters transparent to decoration, and provide
176+
a `@overridable` decorator either in Ember or as an independent package.

0 commit comments

Comments
 (0)