Skip to content

Commit

Permalink
Commit generated default dependencies as they're read
Browse files Browse the repository at this point in the history
- Move `this._computedBindings = {}` out of `_setupComputes` and into
  `setup`
- Overwrite `_get` in `_setupDefaults` to set defaults with `this.attr`
  when they're read in defaultGenerators
- Prevent the calling of a defaultGenerators if its value is passed in on
  instantiation
- Create a test for reading defaults with `this.attr` in
  defaultGenerators
- Create a test for defining defaults on instantiation
  • Loading branch information
Chris Gomez committed Oct 28, 2014
1 parent 819bb1a commit 101e308
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 26 deletions.
1 change: 1 addition & 0 deletions list/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ steal("can/util", "can/map", "can/map/bubble.js",function (can, Map, bubble) {
this.length = 0;
can.cid(this, ".map");
this._init = 1;
this._computedBindings = {};
this._setupComputes();
instances = instances || [];
var teardownMapping;
Expand Down
44 changes: 41 additions & 3 deletions map/define/define.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,50 @@ steal('can/util', 'can/observe', function (can) {


var oldSetupDefaults = can.Map.prototype._setupDefaults;
can.Map.prototype._setupDefaults = function () {
can.Map.prototype._setupDefaults = function (obj) {
var defaults = oldSetupDefaults.call(this),
Map = this.constructor;
propsCommittedToAttr = {},
Map = this.constructor,
originalGet = this._get;

// Overwrite this._get with a version that commits defaults to
// this.attr() as needed. Because calling this.attr() for each individual
// default would be expensive.
this._get = function (originalProp) {

// If a this.attr() was called using dot syntax (e.g number.0),
// disregard everything after the "." until we call the
// original this._get().
prop = (originalProp.indexOf('.') !== -1 ?
originalProp.substr(0, originalProp.indexOf('.')) :
prop);

// If this property has a default and we haven't yet committed it to
// this.attr()
if ((prop in defaults) && ! (prop in propsCommittedToAttr)) {

// Commit the property's default so that it can be read in
// other defaultGenerators.
this.attr(prop, defaults[prop]);

// Make not so that we don't commit this property again.
propsCommittedToAttr[prop] = true;
}

return originalGet.apply(this, arguments);
};

for (var prop in Map.defaultGenerators) {
defaults[prop] = Map.defaultGenerators[prop].call(this);
// Only call the prop's value method if the property wasn't provided
// during instantiation.
if (! obj || ! (prop in obj)) {
defaults[prop] = Map.defaultGenerators[prop].call(this);
}
}

// Replace original this.attr
this._get = originalGet;

return defaults;
};

Expand Down
96 changes: 76 additions & 20 deletions map/define/define_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -381,11 +381,11 @@ steal("can/map/define", "can/test", function () {


});

test("getter with initial value", function(){

var compute = can.compute(1);

var Grabber = can.Map.extend({
define: {
vals: {
Expand All @@ -400,14 +400,14 @@ steal("can/map/define", "can/test", function () {
}
}
});

var g = new Grabber();
// This assertion doesn't mean much. It's mostly testing
// that there were no errors.
equal(g.attr("vals").length,0,"zero items in array" );

});


test("serialize basics", function(){
var MyMap = can.Map.extend({
Expand Down Expand Up @@ -445,21 +445,21 @@ steal("can/map/define", "can/test", function () {
}
}
});

var map = new MyMap({name: "foo"});
map.attr("locations", [{id: 1, name: "Chicago"}, {id: 2, name: "LA"}]);
equal(map.attr("locationIds").length, 2, "get locationIds");
equal(map.attr("locationIds")[0], 1, "get locationIds index 0");
equal(map.attr("locations")[0].id, 1, "get locations index 0");

var serialized = map.serialize();
equal(serialized.locations, undefined, "locations doesn't serialize");
equal(serialized.locationIds, "1,2", "locationIds serializes");
equal(serialized.name, undefined, "name doesn't serialize");

equal(serialized.bared, "foo+bar", "true adds computed props");
equal(serialized.ignored, undefined, "computed props are not serialized by default");

});

test("serialize context", function(){
Expand All @@ -476,10 +476,10 @@ steal("can/map/define", "can/test", function () {
serialize: function(){
serializeContext = this;
can.Map.prototype.serialize.apply(this, arguments);

}
});

var map = new MyMap();
map.serialize();
equal(context, map);
Expand All @@ -492,46 +492,102 @@ steal("can/map/define", "can/test", function () {
define: {
name: {
value: 'John Galt',

get: function(obj){
contexts.get = this;
return obj;
},

remove: function(obj){
contexts.remove = this;
return obj;
},

set: function(obj){
contexts.set = this;
return obj;
},

serialize: function(obj){
contexts.serialize = this;
return obj;
},

type: function(val){
contexts.type = this;
return val;
}
}

}
});

var map = new MyMap();
map.serialize();
map.removeAttr('name');

equal(contexts.get, map);
equal(contexts.remove, map);
equal(contexts.set, map);
equal(contexts.serialize, map);
equal(contexts.type, map);
});

test("value generator is not called if default passed", function () {
var TestMap = can.Map.extend({
define: {
foo: {
value: function () {
throw '"foo"\'s value method should not be called.';
}
}
}
});

var tm = new TestMap({ foo: 'baz' });

equal(tm.attr('foo'), 'baz');
});

test("value generator can read other properties", function () {
var NumbersMap = can.Map.extend({
numbers: [1, 2, 3],
define: {
definedNumbers: {
value: [4, 5, 6]
},
generatedNumbers: {
value: function () {
return new can.List([7, 8, 9]);
}
},
firstNumber: {
value: function () {
return this.attr('numbers.0');
}
},
middleNumber: {
value: function () {
return this.attr('definedNumbers.1');
}
},
lastNumber: {
value: function () {
return this.attr('generatedNumbers.2');
}
}
}
});

var n = NumbersMap();
var prefix = 'was able to read dependent value from ';

equal(n.attr('firstNumber'), 1,
prefix + 'traditional can.Map style property definition');
equal(n.attr('middleNumber'), 5,
prefix + 'Define plugin style default property definition');
equal(n.attr('lastNumber'), 9,
prefix + 'Define plugin style generated default property definition');
});

});
1 change: 1 addition & 0 deletions map/lazy/lazy.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ steal('can/util', './bubble.js', 'can/map', 'can/list', './nested_reference.js',
can.cid(this, ".lazyMap");
// Sets all `attrs`.
this._init = 1;
this._computedBindings = {};
this._setupComputes();
var teardownMapping = obj && can.Map.helpers.addToMap(obj, this);

Expand Down
7 changes: 4 additions & 3 deletions map/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,10 @@ steal('can/util', 'can/util/bind','./bubble.js', 'can/construct', 'can/util/batc
can.cid(this, ".map");
// Sets all `attrs`.
this._init = 1;
// It's handy if we pass this to comptues, because computes can have a default value.
var defaultValues = this._setupDefaults();
this._computedBindings = {};

// It's handy if we pass this to computes, because computes can have a default value.
var defaultValues = this._setupDefaults(obj);
this._setupComputes(defaultValues);
var teardownMapping = obj && can.Map.helpers.addToMap(obj, this);

Expand All @@ -288,7 +290,6 @@ steal('can/util', 'can/util/bind','./bubble.js', 'can/construct', 'can/util/batc
// Sets up computed properties on a Map.
_setupComputes: function () {
var computes = this.constructor._computes;
this._computedBindings = {};

for (var i = 0, len = computes.length, prop; i < len; i++) {
prop = computes[i];
Expand Down
18 changes: 18 additions & 0 deletions map/map_benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,22 @@ steal('can/map', 'can/list', 'can/test/benchmarks.js', function (Map, List, benc
map = new can.Map();
map.attr('obj', objects);
});

var NumbersMap;
benchmarks.add('Overwriting defaults', function () {
NumbersMap = can.Map.extend({
numbers: [1, 2, 3, 4, 5, 6],
foo: 'string',
bar: {},
zed: false
});
}, function () {
new NumbersMap();
new NumbersMap({
numbers: ['a', 'b', 'c', 'd']
});
new NumbersMap({
foo: 'blah blah blah'
});
});
});

0 comments on commit 101e308

Please sign in to comment.