Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions 0000-read-only-cp-syntax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
- Start Date: 2014-19-30
- RFC PR: (leave this empty)
- Ember Issue: (leave this empty)

# Summary

Getter only computed properties should be readOnly by default

# Motivation

Overridable CP's are unexpected, hard to learn, and wanted in the minority of
cases. When it happens unexpeditly it is nearly always unwanted and the resulting
Copy link
Member

Choose a reason for hiding this comment

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

s/unexpeditly/unexpectedly/

chaos is hard to debug.


```js
var User = Ember.Object.extend({
isOld: Ember.computed('age', function() {
return this.get('age') > 30;
});
});

var user = User.create({
age: 30
});

user.get('isOld'); // => false

user.set('age', 31);
user.get('isOld'); // => true

user.set('isOld', false);
user.get('isOld'); // => false

user.set('age', 30);
user.get('isOld'); // => false (EWUT?)
```


What just happened:

* although age > 30 : isOld remains false
* by explicitly setting isOld, we have diverged the CP to no longer invalidate based on its dependentKey.
* many bugs and hair loss
* no way to sensibly recover
* rarely wanted
Copy link
Member

Choose a reason for hiding this comment

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

I'd remove this line. I also think you're missing the original motivation, which was to encapsulate a pattern that was extremely common before we added this behavior, which was something like:

Ember.Object.extend({
  version: function(key, value) {
    if (arguments.length >= 2) {
      this._version = value;
      return value;
    } else if (this._version) {
      return this._version;
    } else {
      return 1;
    }
  }.property()
})

This kind of lazy default was actually reasonably common, and I don't think it's a use-case we should pretend is completely obscure and absurd.

Copy link
Member Author

Choose a reason for hiding this comment

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

As soon as someone adds a dependent key it does become absurd.

Your example illustrates this, as adding a dependent key would correctly purge the cache if it invalidated, unfortunately today's behavior when adding a dependent key would not purge the cache and leave the CP in an unexpected state.



# Detailed design
Copy link
Member

Choose a reason for hiding this comment

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

You should be clear, up front, about the detailed design in 1.x and the ultimate design in 2.0.


```js
firstName: Ember.computed(function() {

}); // readOnly
Copy link
Member

Choose a reason for hiding this comment

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

You should explain what "read only" means; specifically the fact that it's an exception and not a black-hole.

```

Is equivalent to:

```js
firstName: Ember.computed({
get: function() {
Copy link
Member

Choose a reason for hiding this comment

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

You haven't described the fully verbose version yet so you can't really refer to this as being equivalent to it. I'd reorder this section to explain the full set / get.

}); // readOnly (same as above)
```

---

```js
firstName: Ember.computed(function() {

}).overridable(); // writable
Copy link
Member

Choose a reason for hiding this comment

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

You should explain exactly what overridable means before showing this code.

```

Is equivalent to:

```js
firstName: Ember.computed({
get: function() {
}).overridable(); // writable
```

---


```js
firstName: Ember.computed(function(key, value) {

}).overridable(); // throw new Error("Overridable cannot be used if a setter already exists....");
```

Is equivalent to:

```js
firstName: Ember.computed({
get: function() { },
set: function() { }
Copy link
Member

Choose a reason for hiding this comment

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

I thought I remembered us going with:

firstName: Ember.computed({
+  get: function() { },
+  set: overridable
})

Choose a reason for hiding this comment

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

I prefer what wycats wrote above, if this is the only way to create the clobbering setter there is no need for the assertion, if we add the other it should at least decompose to this.

}).overridable(); // throw new Error("Overridable cannot be used if a setter already exists....");
```

---

```js
firstName: Ember.computed({
get: function() { },
set: function() { }
}).readOnly(); // throw new Error("Cannot mark a CP as readOnly if it has an explicit setter");
```

```js

firstName: Ember.computed({
get: function() { }
}); // on super class

firstName: Ember.computed({
get: function() { },
set: function(key, value) { this._super(key, value); }
}); // throw new Error("ReadOnly Error blah blah blah");
```

# migration:

* 1.x update internals to correctly use `writable` if needed (and runs tests against this)
Copy link
Member

Choose a reason for hiding this comment

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

This really needs to be fleshed out in more detail. I could write it up if you want?

Copy link
Member Author

Choose a reason for hiding this comment

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

sure, that would be great

Copy link
Member Author

Choose a reason for hiding this comment

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

@wycats ping?

Choose a reason for hiding this comment

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

@wycats this ended with "I could write it up if you want?" if you don't have time can we move forward in some way?

Copy link
Contributor

Choose a reason for hiding this comment

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

I already started to implement this. The implementation is very simple in fact, but there is loads of failing tests. It requires some thought to decide which internal properties should be writable.

* 1.x deprecate writability unless `writable` was explicitly called, or new `set` syntax was used
* 2.0 flip the switch to `readOnly` by default and never look back.

# Drawbacks

N/A

# Alternatives

N/A

# Unresolved questions

N/A