sylvainpolletvillard/ObjectModel

Is there a way to set a default prototype?

sylvainpolletvillard opened this issue · 8 comments

Is there a way to set a default prototype? The following doesn't seem to work.

const {EventEmitter} = require('events')
const Crypto = ObjectModel({
  name: String
}).defaultTo(EventEmitter.prototype)

I'm setting the prototype as follows but it would be nice to have a default.

Object.setPrototypeOf(
  Crypto({
    name: 'Tezos'
  })
  , EventEmitter.prototype
)

Originally posted by @RichAyotte in #93 (comment)

It looks like base is hardcoded to Object, if I'm reading this correctly.

return initModel(model, ObjectModel, def, Object)

Is setting the prototype a feature that you'd consider?

So, to make things clear and avoid confusion:

  • model instances inherit from the model prototype, that's why you put methods and other stuff in the prototype property of the model constructor, i.e. Crypto.prototype
  • with this prototype link, you get Crypto({ name: "Tezos" }) instanceof Crypto === true
  • if you reassign completely the prototype with Object.setPrototypeOf, you lose this link. This can cause some issues, for example if you have defined other stuff in ObjectModel.prototype or Model.prototype
  • the defaultTo is to used to set a default value to pass to the constructor when it is called without arguments. It does not change the model prototype and will not be used if a value is specified in the model constructor.

So the best option I think for your usecase is to use the extend method:
const Crypto = ObjectModel({ name: String }).extend(EventEmitter)

Basically it is equivalent to Object.assign(Crypto.prototype, EventEmitter.prototype) but let you override the model prototype afterwards if needed.

That definitely works. Not sure how I missed the extend method. The prototype remains Model but mixed in with EventEmitter prototype which is fine. Thank you for the detailed and quick response.

No problem, glad to help

@sylvainpolletvillard I spoke too fast. As soon as I tried to listen for an event, I got a TypeError. Here's the MWE.

'use strict'

const {EventEmitter} = require('events')
const {ObjectModel} = require('objectmodel')

const Bar = ObjectModel({
	name: String
}).extend(EventEmitter)

const bar = Bar({
	name: 'Foo'
})

bar.on('data', console.log)
bar.emit('data', 'hello world')

Error:

/node_modules/objectmodel/dist/object-model.js:102
		isModelInstance = i => i && is(Model, getProto(i).constructor),
		                                                 ^

TypeError: Cannot read property 'constructor' of null
    at isModelInstance (/node_modules/objectmodel/dist/object-model.js:102:52)
    at cast (/node_modules/objectmodel/dist/object-model.js:256:69)
    at getProxy (/node_modules/objectmodel/dist/object-model.js:288:36)
    at newPath (/node_modules/objectmodel/dist/object-model.js:332:38)
    at controlMutation (/node_modules/objectmodel/dist/object-model.js:226:5)
    at Object.set (/node_modules/objectmodel/dist/object-model.js:331:13)
    at _addListener (events.js:217:29)
    at Proxy.addListener (events.js:271:10)
    at Object.proxifyFn [as apply] (/node_modules/objectmodel/dist/object-model.js:292:26)
    at Object.<anonymous> (/src/ee-test.js:24:5)

Maybe you can shed some light on the importance of this check?

isModelInstance = i => i && is(Model, getProto(i).constructor),

Would the following be a safe fix or would it potentially cause more problems?

isModelInstance = i => i && getProto(i) && is(Model, getProto(i).constructor),

Ah, right, this lib creates objects with no prototypes (https://github.com/Gozala/events/blob/master/events.js#L86 )

Your suggested fix looks perfect, let me add some tests and release a new patch version

Just released v3.7.7, can you confirm it fixes your issue ?

v3.7.7 works well. Thanks again!