Ember Auto - nicer properties with less this.get("junk")


#1

Hi everyone,

Time to get excited! I’ve just released a new library called Ember Auto for nicer computed properties. It supplies the dependent properties as arguments which means you can just use them instead of this.getting.

So, the new way to do computed properties:

gravatarUrl: function(email, size) {
  return 'http://www.gravatar.com/avatar/' + hex_md5(email) + '?s=' + size;
}.auto("email", "size")

Or in coffeescript:

gravatarUrl: Em.Auto "email", "size", (email, size)->
  'http://www.gravatar.com/avatar/' + hex_md5(email) + '?s=' + size;

I find it much nicer using regular variables than strings to reference properties. It also saves a lot of typing.

Have a look at: https://github.com/gunn/ember-auto and let me know what you think!

To try now, just include https://raw.github.com/gunn/ember-auto/master/ember-auto.js (2260 bytes minified) and start using.


#2

Most of my apps have a pattern of declaring local variables for the various properties I will need to access before any of the actual logic starts. This lib makes all of that ‘extra’ setup code go away.

I’ve been integrating this into an app I am working on, and it has made the code quite a bit easier to read.

Perhaps, after a shakedown period, this could be added to Ember core.

@gunn - Great work!


#3

Very cool indeed. A useful approach and reducing boilerplate code is good.

Also, have you heard of EmberScript?

http://emberscript.com

I only mention it because it also has some convenience methods for abstracting away some of the boilerplate around setters and getters. So for example in coffeescript you have a very succinct shorthand for computed properties with a special syntax.

+computed firstName, lastName

#4

@eccegordo yes I think emberscript’s an incredible project.

I think that for various reasons though not as many people will use it. Even plain coffeescript is a bit divisive.

What I wanted to do with this is make something that people can use to improve their existing codebases without considering tradeoffs.

@rwjblue I’d love for this to be added to core, after a shakedown period as you say. I don’t think there would be any barriers, just consideration about how .property() would exist alongside .auto().


#5

Hey @gunn – this looks very intriguing. I’m a calculated property addict and a coding neat freak and this “problem” has always driven me nuts.

I noticed that you’re not supporting setting properties yet. Are there any ideas on how that might work given this new convention? (arguments.length > 1 would clearly no longer be a valid check).


#6

I am almost wondering if this should be a behavior that is rolled into .property().

To not break the API you could put it behind a global flag

USE_PROPERTY_AUTO_ARGUMENTS = true|false

It does seem like a simplification of the API.

@wycats thoughts?


#7

Yep. We could do it the old style, but dedicating the first three argument places to keyName, value, and oldValue would be very ugly.

@rwjblue suggested putting those options into one object always supplied as the last argument. So perhaps something like:

fullName: function(first, last, meta) {
  if (meta.isSetting) {
    var names = meta.value.split(/\s+/);
    this.set("firstName", names[0]);
    this.set("lastName",  names[1]);

    return meta.value;
  };

  return first + " " + last;
}.auto("firstName", "lastName")

Of course for now you can just use regular properties.


#8

Okay, I’m going to implement setting now. @eccegordo also has me thinking about adding a flag to enable ‘auto’ functionality with .property().


#9

I like that solution. In the spirit of cleaner/shorter syntax (and perhaps a throwaway idea)…what if it looked something like this? That might open the door for some built in patterns for setting calculated properties.

fullName : function(first, last, setter) {
  setter(function(value) {
    var names = value.split(/\s+/);

    this.set("firstName", names[0]);
    this.set("lastName", names[1]);

    return value;
  });

  return first + " " + last;
}.auto("firstName", "lastName")

#10

A bit hacky but I’ve found an approach that doesn’t require repeating the keys, and is minification safe:

someProp: Em.superAuto 'firstName', 'lastName', ->
  firstName + " " + lastName

#11

@alexspeller’s approach looks very promising if we don’t mind using an eval.

One thing we need to decide is how to deal with naming conflicts - we need a way to map the keys name, user.name, and userName to different argument names.

Here are two options:

1: Optionally provide overrides as arguments

greeting: function(name) {
  return "Hi " + name + " your login is: " + userName;
}.auto("user.name", "userName")

2: Use object literals to provide pairs of argNames and keyPaths.

greeting: function() {
  return "Hi " + name + " your login is: " + userName;
}.auto({name: "user.name"}, "userName")

Option 2 also lets us get closer to backwards compatible setters, although the arguments.length > 1 check would still have to change:

fullName: function(keyName, value, oldValue) {
  if (Em.isArg(value)) {
  // rest of function...

  return firstName + " " + lastName;
}.auto("firstName", "lastName")

#12

I personally like the idea of using object literals. It seems reasonably explicit and doesn’t take a lot of cognitive overhead to figure out what is going on. I think it is important to consider people coming to an existing code base and seeing this code, and not knowing much about ember yet. Seems like the object literals would be easiest to grok, IMHO.


#13

I like the direction this is going. Another alternative implementation is to pass the parameters as object literals. The first parameter can contain the auto properties, and the second parameter can contain the setter information. For example:

fullName: function(properties, setter) {
  if (setter) {
    var names = setter.value.split(/\s+/);
    this.set('firstName', names[0]);
    this.set('lastName', names[1]);
    return setter.value;
  }
  return properties.firstName + " " + properties.lastName;
}.auto("firstName", "lastName")

A couple nice benefits:

  • To determine if the property is being set, just check for the existence of the setter. Technically, you could still use the arguments.length > 1 check, but I’ve personally never been a big fan of that syntax. By putting the value within an object it is more logical to identify the different situations. When setter is undefined, the property is not being set. When setter is defined but setter.value is undefined, the property is being set to undefined.
  • There would not be any naming conflicts. The object’s keys would be the entire path of the property. So, in the previous example, you would reference the properties as properties.userName and either properties.user.name or properties['user.name'], depending on the implementation.

#14

On second thought, I think we can do this in a way that is quite compatible with existing code bases and all of the existing Ember knowledge, documentation, examples, tutorials, etc.

Let’s simply hijack the “key” parameter and turn it into an object literal containing the properties. I have never used the key parameter and don’t recall ever seeing anyone else use it. I’m sure there’s a great reason for it and is necessary in some use cases, but it seems like the exception than the rule. We can still provide the key within the object, perhaps as properties.__key to avoid naming conflicts.

The setter value can remain as is, as the second parameter. Therefore, arguments.length > 1 is still the right way to determine if the property is being set.

If this were to eventually replace the existing .property() implementation, then the only problematic code are computed properties where the key parameter is being used. Am I wrong in thinking that this is rarely if ever used? What is the use case where this is necessary?

Revised example:

fullName: function(properties, value) {
  if (arguments.length > 1) {
    var names = value.split(/\s+/);
    this.set('firstName', names[0]);
    this.set('lastName', names[1]);
    return value;
  }
  return properties.firstName + " " + properties.lastName;
}.auto("firstName", "lastName")

I still like the idea of encapsulating the setter value within an object literal as well, but that seems somewhat off-topic now.


#15

Yep, I think Christopher has got the nice solution there.


#16
fullName: function(properties, value) {

I don’t really like this, properties.firstName is no real benefit over this.get('firstName') IMO

Actually I think that setters should be ignored for this use case - they are much rarer than normal CPs. This should be a shorthand for normal read-only CPs with the original syntax still available for more complex use cases.


#17

@gunn now with even less typing (exploiting that keypaths can’t contain spaces so all the props are passed in as one string):


#18

@alexspeller What if the parameter name was something shorter than properties? I named it properties in this example to clarify what it is, but it can obviously be named anything.

fullName: function(p, value) {
  ...
  return p.firstName + ' ' + p.lastName;
  ...

The difference here is p.firstName as opposed to firstName. IMO significantly better than this.get('firstName'). It may not be quite as beautiful as firstName but IMO it gets you 90% of the way there (cleaner, static variable as opposed to function call) and maintains compatibility with setters.


#19

@alexspeller and @gunn, I think we’ve got a good candidate for being added to core here, but not if it’s too ‘magic’, and probably not using eval(). I really like Christopher’s solution, it’s very clear what’s happening, it’s backwards compatible and can even be ignored if you like, and provides support for setting as well.


#20

@simonvwade yes I’m definitely thinking about what would make sense in core ember while looking at the options.

So, I’ll favour choices with less magic, that are closer conceptually and in terms of compatibility to the existing computed properties.

I’d rather avoid the p.firstName style if we can although it sure does beat this.get(‘firstName’).

Also the suggestion as it is could run into trouble if you have keys like user and user.stats.age - user can’t just be an almost empty object, it has to be the full user instance. The p["user.stats.age"] syntax would avoid this but then we’re back to using strings and it isn’t as much of an improvement.

It also doesn’t allow backwards compatible setter syntax, or using shorter names which is a nice-to-have. It could be combined with the object literal syntax though - {name: "user.name"}...

The big question for me now is if an eval or new Function would be acceptable. If so, this syntax seems the best of all worlds:

greeting: function() {
  return "Hi " + name + " your login is: " + userName;
}.auto({name: "user.name"}, "userName")

It even gives us 95% backwards compatible setters.