Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support init or setup returning something other than new instance #30

Merged
merged 5 commits into from
Jan 26, 2017
Merged
Show file tree
Hide file tree
Changes from 4 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
85 changes: 83 additions & 2 deletions can-construct.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ assign(Construct, {
writable: true
});
args = inst.setup.apply(inst, arguments);
if (args instanceof Construct.ReturnValue){
return args.value;
}
inst.__inSetup = false;
}
// Call `init` if there is an `init`
Expand Down Expand Up @@ -526,6 +529,49 @@ assign(Construct, {
* dog.speak(); // 'woof'
* blackMamba.speak(); // 'ssssss'
* ```
*
* ## Alternative value for a new instance
*
* Sometimes you may want to return some custom value instead of a new object when creating an instance of your class.
* For example, you want your class to act as a singleton, or check whether an item with the given id was already
* created and return an existing one from your cache store (e.g. using [can-connect/constructor/store/store]).
*
* To achieve this you can return [can-construct.ReturnValue] from `setup` method of your class.
*
* Lets say you have `myStore` to cache all newly created instances. And if an item already exists you want to merge
* the new data into the existing instance and return the updated instance.
*
* ```
* const myStore = {};
*
* const Item = Construct.extend({
* setup: function(params){
* if (myStore[params.id]){
* var item = myStore[params.id];
*
* // Merge new data to the existing instance:
* Object.keys( params ).forEach(function( key ){
Copy link
Contributor

Choose a reason for hiding this comment

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

Could probably use Object.assign here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

* item[key] = params[key];
* });
*
* // Return the updated item:
* return new Construct.ReturnValue( item );
* } else {
* return [params];
* }
* },
* init: function(params){
* this.id = params.id;
Copy link
Contributor

Choose a reason for hiding this comment

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

Might use Object.assign here too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

* this.name = params.name;
*
* // Save to cache store:
* myStore[this.id] = this;
Copy link
Contributor

Choose a reason for hiding this comment

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

This should almost certainly be done in setup. init is usually left for people inheriting from Item to use for their particular Item type.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

* }
* });
*
* const item_1 = new Item( {id: 1, name: "One"} );
* const item_1a = new Item( {id: 1, name: "OnePlus"} )
* ```
*/
extend: function (name, staticProperties, instanceProperties) {
var shortName = name,
Expand Down Expand Up @@ -663,6 +709,40 @@ assign(Construct, {
* console.log(Counter.count); // 1
* ```
*/
},
/**
* @function can-construct.ReturnValue ReturnValue
* @parent can-construct.static
*
* A constructor function which `value` property will be used as a value for a new instance.
Copy link
Contributor

Choose a reason for hiding this comment

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

Too much detail for a description. It shouldn't care at all about the value property. Maybe something like:

Use to overwrite the return value of new Constructor(...).

*
* @signature `new Construct.ReturnValue( value )`
*
Copy link
Contributor

Choose a reason for hiding this comment

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

The signature should talk about how this works. Also, the example code below should be put in the signature.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

* @param {*} value A value to be used for a new instance instead of a new object.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the value MUST be an Object type. If someone tries to return a number, it won't work.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, indent 2 spaces @param and other nested @ tags.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

*
* ```
* const Student = function( name, school ){
* this.name = name;
* this.school = school;
* }
*
* const Person = Construct.extend({
* setup: function( options ){
* if (options.school){
* return new Constructor.ReturnValue( new Student( options.name, options.school ) );
* } else {
* return [options];
* }
* }
* });
*
* const myPerson = new Person( {name: "Peter", school: "UFT"} );
*
* myPerson instanceof Student // => true
* ```
*/
ReturnValue: function(value){
this.value = value;
}
});
/**
Expand All @@ -675,8 +755,9 @@ assign(Construct, {
*
* @param {*} args The arguments passed to the constructor.
*
* @return {Array|undefined} If an array is returned, the array's items are passed as
* arguments to [can-construct::init init]. The following example always makes
* @return {Array|undefined|can-construct.ReturnValue} If an array is returned, the array's items are passed as
* arguments to [can-construct::init init]. If [can-construct.ReturnValue] is returned then the value that passed to it
* will be used as a value for a new instance. The following example always makes
Copy link
Contributor

Choose a reason for hiding this comment

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

Change:

If [can-construct.ReturnValue] is returned then the value that passed to it
will be used as a value for a new instance.

To something more like:

If a [can-construct.ReturnValue] instance is returned, the ReturnValue instance's
value will be returned as the result of calling new Constructor().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

* sure that init is called with a jQuery wrapped element:
*
* ```js
Expand Down
35 changes: 35 additions & 0 deletions can-construct_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,38 @@ QUnit.test("setters not invoked on extension (#28)", function(){
extending = false;
new Base().something = "foo";
});

QUnit.test("return alternative value simple", function(){
var Alternative = function(){};
var Base = Construct.extend({
setup: function(){
return new Construct.ReturnValue( new Alternative() );
}
});
QUnit.ok(new Base() instanceof Alternative, "Should create an instance of Alternative");
});

QUnit.test("return alternative value on setup (full case)", function(){
var Student = function(name, school){
this.name = name;
this.school = school;
this.isStudent = true;
};
var Person = Construct.extend({
setup: function(opts){
if (opts.age >= 16){
return new Construct.ReturnValue( new Student(opts.name, opts.school) );
}
opts.isStudent = false;
return [opts];
},
init: function(params){
this.age = params.age;
this.name = params.name;
this.isStudent = params.isStudent;
}
});
QUnit.equal(new Person({age: 12}).isStudent, false, "Age 12 cannot be a student");
QUnit.equal(new Person({age: 30}).isStudent, true, "Age 20 can be a student");
QUnit.ok(new Person({age: 30}) instanceof Student, "Should return an instance of Student");
});