stampit-org / stampit

OOP is better with stamps: Composable object factories.
https://stampit.js.org
MIT License
3.02k stars 102 forks source link

Make it easier to customize instance initialization #15

Closed RainerAtSpirit closed 10 years ago

RainerAtSpirit commented 11 years ago

If I see that correctly than properties passed in to the factory will be mixIn as instance properties. https://github.com/stampit-org/stampit/blob/master/stampit.js#L77 So {a: 'a', b: 'b'} become this.a = 'a' and this.b = 'b'. So far so good, but there's no way any longer to access them as object.

factory = function factory(properties) {
    var state = merge({}, fixed.state),
    instance = mixIn(create(fixed.methods || {}),
    state, properties),
    closures = fixed.enclose;

Is there a way to preserve them somehow, so that they can be accessed within closures for some kind of housekeeping/processing?

ericelliott commented 11 years ago

Well, they're always available on the stamp, but the stamp is not referenced in the object instances (by design).

Could you supply a good use case for this?

RainerAtSpirit commented 11 years ago

Still trying to get my heads around using the stampit factory/mixin approach. Let's take a typical Backbone.model extend like e.g.

var Base= sp.Item.extend({
    'site': 'sites/teamsite',
    'list': 'Tasks'
}),
CustomItem = Base.extend({
'params': {
    expand: 'Status'
},
task = new CustomItem ({ Title: 'MyTask', ... });

I've created a small straw man sp.item stamp and use it like below.

var base = sp.stampit().state({
    site: 'sites/teamsite', 
    list: 'Tasks'
}),

custom = sp.stampit().state({
    params: {
        expand: 'Status'
    }
}),

task = sp.stampit.compose(sp.item, base, custom);

task.create({ Title: 'MyTask'...});

I think this last step gives me some trouble. What I would like to accomplish is that everything that's passed into create is accessible as one properties object. Instead all key/val pairs become instance properties.

One way to address this would be to tell end user to use .create({properties: {Title 'MyTask', ...}}) ... but that's hardly going to work. Another option would be to not passing in anything into create and have a dedicated properties getter/setter instead. Third option would be that I get access to the properties object during create. Not sure though if that's a good use case or not...

ericelliott commented 11 years ago

My question is, why do you need that properties object at all? Inside .enclose(function () {});, this gives you access to everything that was passed into create, along with all of the .state() mixins...

So, what's the difference between your dream properties object, and this?

Are you trying to make those properties private?

currently, you can do that by running through them in an .enclose(function () {});, and assigning them to a private variable, and then deleting the properties from this.

Are you asking for sugar to accomplish the same thing?

RainerAtSpirit commented 11 years ago

Sugar, yeah probably. Maybe I'm mistaken , but it seems that only .create({properties: {Title: 'MyTask', ...}}) would provide access to ONE properties property within .enclose. Pretty easy to deal with it that way in .enclose.

If using .create({Title: 'MyTask', ...}) instead .enclose would provide access to the Title property in addition to an unknown number of other key/value pairs that have been passed in. Not that easy to deal with it as now you've to dissect passed in props from .state mixins.

ericelliott commented 11 years ago

Yeah, basically, but remember I need to balance convenience for you vs reusability for the community at large. So, any proposed sugar would need to be generally reusable, keeping in mind that I can't just pass parameters willy-nilly into .enclose(fn), because functions designed for oldskool constructors might try to interpret them incorrectly...

I also can't simply munge this with special keys. Users need to have full control over the instance objects that get created.

ericelliott commented 11 years ago

Maybe I could do this with a helper mixin. Here's some dream code that would let you specify the behavior of specific keys:

var mutator = stampit.mutator('secret', 'secret2', function (properties) {
  properties.private(); // make the properties private
  this.get = function (key) {
    return properties[key];
  }
  this.set = function (key, val) {
    properties[key] = val;
  }
});

This could default to all keys:

var vault = stampit.mutator(function (properties) {
  properties.private(); // make ALL properties private
  this.get = function (key) {
    return properties[key];
  }
  this.set = function (key, val) {
    properties[key] = val;
  }
});

vault would be a stamp that you can compose just like any other stamp.

var stamp = stampit.compose(base, mixin, vault);
var myThing = stamp({key: 'key to the kingdom'});
myThing.key; // undefined
myThing.get('key'); // 'key to the kingdom'

This is all dream code that hasn't been written yet, but it could be added to stampit. Would that be helpful?

RainerAtSpirit commented 11 years ago

Specifying specific behavior for one, multiple or all props would be benefical... at least for my use case.

ericelliott commented 11 years ago

Cool... If you're interested in implementing the mutator function as described, I'd be willing to accept a pull request. Otherwise it will have to wait a little bit. I have some things to wrap up on my book, first.

RainerAtSpirit commented 11 years ago

I might have some time later this week to give it a shot. In the meantime can you take a look at the current status of the saucelabs tests. I'm not a saucelabs pro, but got most of the tests running/passing in Ubuntu and Windows 7. On both platforms Linux/Android failed and 2008/Firefox gave an error though. Would be great to have a all green situation before I start.

ericelliott commented 11 years ago

Yeah, I'll have to take a look a little bit later. Sometimes the tests fail because if you don't have a pro account, you can't run more than one test at a time reliably. I need to edit the config so that it won't try to run them all at once.

koresar commented 10 years ago

Eric, as far as I understand all the people want is to make state(s) private when instantiating an object. Similar to classic constructors. Issues number 18, 15, 11, 10.

I would like to propose following 5 lines of changes: http://screencloud.net/v/q0QE (untested)

As the result we retain compatibility with the already existing API. Also following syntax would be possible:

var factory = stampit.enclose(function (fooParam, barParam) {
  var fooParam = fooParam, bar = barParam;
});

var newObjectWithPrivateState = factory.construct('foo', 'bar');

I saw all your talks, read your book (thanks!), and I bet you likely to reject my idea. Anyways, what do you think?

jtenner commented 10 years ago

@koresar I had to deal with this limitation myself, so I wrote something similar to stampit for my own personal use.

StarShine does something similar.

Accepting parameters inside the closures is definitely useful.

ericelliott commented 10 years ago

@koresar That is probably the cleanest proposal I have seen, yet, and it's better than my own proposed solution, as well.

Please submit a pull request, and make sure that all the tests still pass.

koresar commented 10 years ago

Done.

@jtenner Thank you, looks nice. However, I am fine with stampit for now.

ericelliott commented 10 years ago

Stampit supports passing arbitrary parameters now, but please use with caution.