Ticket #2530093 (closed task)

Reporter


Eric Ferraiuolo
Opened: 04/6/11
Last modified: 08/18/11
Status: closed
Type: task
Resolution: fixed

Owner


Satyen Desai
Target Release: 3.4.0
Priority: P3 (normal)
Summary: [Pull Request] - Make Y.Attribute augmentation by not forcing the call to addAttrs in the Attribute constructor when the instance isn't a Y.Node instance.
Description:

Recently when creating a lower-level class (non Y.Base-based) with Y.Attribute support I ran into issues Y.augmenting my class with Attribute and also just trying to mix Y.Attribute's prototype with
my class'.

Inside the Y.Attribute constructor (which does useful stuff that I'd like to call) there's a forced call to `addAttrs` if the instance object (`this`) is not Y.Base-based. The reason this is a problem
is when you want to also include the user-provided values for the attributes at the time of construction with the call to `addAttrs`.

Inside Y.Base's constructor you see a call to `Y.Attribute(this)` and later a call during initialization to `this.addAttrs` where the user-provided values are passed to `addAttrs` along with the ATTRS
static configuration.

In my class' constructor if I call `Y.Attribute.call(this)` (or let Y.augment call it for me) I'm forced to have `this.addAttrs` called on me because my class is not a Y.Base subclass. Looking at the
code comment, it appears this code path was added for Y.Node support, but why not _just_ check to see if `this` instanceof Y.Node?

If the reason this code path is added to provide a quick way to add Y.Attribute support via `Y.augment`ing your class, then the Y.Attribute constructor should also optionally take in the user-provide
attribute values in the constructor: `Y.Attribute(userValues)` this way the Y.augment code path would be unchanged as no `userValues` object would be passed in, but would allow developers to not use
the fancy Y.augment, but instead use Y.mix and chain the call to the Y.Attribute constructor manually allowing them to pass on the user-provided attribute values that their class was originally
constructed with.

So not sure which approach you think would be better? A stricter conditional check for an instanceof Y.Node around the call to `this.addAttrs` in Y.Attribute's constructor, or a
`Y.Attribute(userValues)` signature which would pass on `userValues` to the call to `this.addAttrs` when the instance object is not an instance of Y.Base?

To get around this issue, I had to copy almost the entire Y.Attribute constructor logic and repeat it in my code so that I could make the call to `this.addAttrs` the way I wanted to, by passing on the
user-provided attribute values when calling the fn.

Type: task Observed in Version: development master
Component: Attribute Severity: S3 (normal)
Assigned To: Satyen Desai Target Release: 3.4.0
Location: Priority: P3 (normal)
Tags: pull request, github Relates To:
Browsers: All
URL: https://github.com/ericf/yui3/commit/7903173b2c5e64c69cccd6bf3a79a2a8e68965a7
Test Information:

Change History

Eric Ferraiuolo

YUI Developer

Posted: 04/6/11

Satyen Desai

YUI Developer

Posted: 04/27/11
  • milestone changed to 3.4.0
  • status changed from new to accepted

Satyen Desai

YUI Developer

Posted: 04/28/11
  • milestone changed from 3.4.0 to 3.4.0 PR1

George

YUI Developer

Posted: 05/15/11
  • milestone changed from 3.4.0 PR1 to 3.4.0

George

YUI Developer

Posted: 05/15/11
  • estimated changed from 0 to 1
  • remaining changed from 0 to 1
  • sprint changed to 3.4.0 PR1

Satyen Desai

YUI Developer

Posted: 05/17/11

Hey Eric,
Just started to look at rolling this in.

So your use case has a non-Base based object which also sets up it's own ATTRS static property? It seems like those 2 things combined are what is getting in your way (that is, if you didn't have an ATTRS property, you wouldn't hit the addAttrs code block either). I'm hesitant to add a Node specific check to attribute, so I won't pull in the pull-request as is, but something along the lines of your alternate solution seems like the better way to go.

Assuming you're consciously using the static ATTRS support on the constructor built into Attribute (it was added as a hook for Node, but the intention was to keep in generic), the following seem like the 3 options on the table to support your use case:

1) Y.Attribute = function(userValues) {
...
attrs = this.constructor.ATTRS;

if (attrs && !Base) {
this.addAttrs(attrs, userValues);
}
}

This is what you were proposing.

2) Y.Attribute = function(attrs, userValues) {
...
attrs = attrs || this.constructor.ATTRS;
this.addAttrs(attrs, userValues);
}

3) Move the addAttrs stuff out of the constructor, and into a separate method, which you can override and no-op. This seems like a good idea in general, and I can't see the performance impact being that great (which would be the only thing I'd pause on before extracting it).

Let me know if one suits your needs better than another. My preference would be to implement both 2) and 3). The signature for 2) seems cleaner than 1) generically speaking - forgot to add, of course it does mean null would get passed in for the this.constructor.ATTRS use case.

Satyen Desai

YUI Developer

Posted: 05/17/11

Planning to go with this [ unit testing right now ].


function Attribute(attrs, values) {

var host = this; // help compression

// Perf tweak - avoid creating event literals if not required.
host._ATTR_E_FACADE = {};

EventTarget.call(host, {emitFacade:true});

// _conf maintained for backwards compat
host._conf = host._state = new Y.State();

host._stateProxy = host._stateProxy || null;
host._requireAddAttr = host._requireAddAttr || false;

this._initAttrs(attrs, values);
}

/**
* Utility method to set up initial attributes defined during construction, either through the constructor.ATTRS property, or explicitly passed in.
*
* @method _initAttrs
* @protected
* @param attrs {Object} The attributes to add during construction (passed through to <a href="#method_addAttrs">addAttrs</a>). These can also be defined on the constructor being augmented with Attribute by defining the ATTRS property on the constructor.
* @param values {Object} The initial attribute values to apply (passed through to <a href="#method_addAttrs">addAttrs</a>). These are not merged/cloned. The caller is responsible for isolating user provided values if required.
*/
_initAttrs : function(attrs, values) {
// ATTRS support for Node, which is not Base based
attrs = attrs || this.constructor.ATTRS;

var Base = Y.Base;
if ( attrs &amp;&amp; !(Base &amp;&amp; Y.instanceOf(this, Base))) {
this.addAttrs(this._protectAttrs(attrs), values);
}
}

Satyen Desai

YUI Developer

Posted: 05/17/11
  • completed changed from 0 to 1
  • status changed from accepted to checkedin

Satyen Desai

YUI Developer

Posted: 05/17/11
  • resolution changed to fixed

Added param support to constructor. Fixes #2530093. Added unit tests too (just like Ryan does)
View Commit: c4cf29edebef0d1c75ccfa55f67b2549c235bf8e

Eric Ferraiuolo

YUI Developer

Posted: 05/17/11

Satyen, will this still work for making ATTRS lazyAdd? I was passing `true` to `addAttrs`. Also I guess this new setup will still work for people using Y.Augment with Y.Attributes

Satyen Desai

YUI Developer

Posted: 05/18/11

So, it doesn't pass through lazy. I can add that too, although that was the debate I was having - about having to keep the constructor args in sync with addAttrs. At one point I was passing through all of them then backed off, thinking that if you needed lazy you could override the _initAttrs method. Let me know what you think.

It will still work with folks using the formal Augment [ the unit tests have cases ]

Satyen Desai

YUI Developer

Posted: 05/18/11

btw, why not just use Base? That's really it's role - to manage the static ATTRS collection, lazy construction etc..

Eric Ferraiuolo

YUI Developer

Posted: 05/18/11

Using Y.Attribute is incredibly useful and is "marketed" to developers as a stand-alone feature to mix-in/augment into their classes, and when using lazyAdd has high performance. When working on a Model (MVC) implementation I ran into blocking performance issues with the Y.Base-based impl, and decided to create a native constructor fn and mix-in Y.Attribute. The result was something more than an order of magnitude faster and much less complex (30x less fn calls).

I am now working with Ryan since we both realized we are woking towards the same end-goal with the Model implementation and both were basing things on Backbone.js, so now we're collaborating on it. Ryan also saw the performance hit with Y.Base, but it's clear there's a lot of time taken up in Y.mix and he is currently looking into speeding up `mix`.

That said, in general I'd prefer to use Y.Base for most things, but when creating hundreds or even thousands of instances of a class, Y.Base is too expensive as it currently stands. I'd love to see the init lifecycle of Y.Base become more performant, and if Y.mix optimizations are low-hanging fruit that would be a big win for Base and the library as a whole.

Satyen Desai

YUI Developer

Posted: 05/18/11

Added lazy to constructor signature. See #2530093
View Commit: a3f56f4ad020943d79eeefdf0eb465a1b02d3c2a

Satyen Desai

YUI Developer

Posted: 05/18/11

I'll be looking at Base runtime performance in general for our second 3.4.0 sprint. The need to support 1000s of Base based instances on a page has come up before. A large part of it may be coming from the event stack [ 1000 publishes for init, 1000 fires for init ]. Based on some tests I tried with Tripp, that cuts down about 50% of the instantiation time. However the other 50% is still coming from Base, and needs looking into. All this is based on ad-hoc testing - no concrete numbers yet. A flyweight pattern for this type of scale may be something we need to look into also.

Eric Ferraiuolo

YUI Developer

Posted: 05/18/11

This is great, and let me know how I can help. I too realized it was the event stack which was causing the problems since `.publish()` is expensive, when looking deeper `Y.mix` seems to be in part the reason why it's so expensive to call `publish` and one reason Ryan was talking about doing a deep-dive to update mix.

I guess I'm unclear what the flyweight pattern would look like, I'd me interested in hearing more about your thoughts here (maybe this ticket isn't the best place :), I remember you mentioning this pattern with Widget parent/child too...

Jenny Donnelly

YUI Developer

Posted: 06/2/11
  • milestone changed from 3.4.0 to 3.4.0 PR1

George

YUI Developer

Posted: 06/8/11
  • milestone changed from 3.4.0 PR1 to 3.4.0

George

YUI Developer

Posted: 06/8/11
  • sprint changed from 3.4.0 PR1 to sprint 1

George

YUI Developer

Posted: 08/18/11
  • status changed from checkedin to closed