-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Document methods are not bind to the document #791
Comments
I could not reproduce with the attached test. |
For the following code I'm getting an output:
const RxDB = require('rxdb');
RxDB.plugin(require('pouchdb-adapter-idb'));
RxDB.plugin(require('pouchdb-adapter-memory'));
async function test() {
const db = await RxDB.create({
name: 'testdb',
adapter: 'idb',
multiInstance: false
});
const schema = {
version: 0,
type: 'object',
primaryPath: '_id',
properties: {
name: {
type: 'string'
}
}
};
const collection = await db.collection({
name: 'person',
schema: schema,
methods: {
hello() {
return this.name;
}
}
});
// Insertion
await collection.insert({ name: 'hi' });
const item = await collection.findOne().exec();
const hello = item.hello;
console.log('In:', item.hello());
console.log('Out:', hello());
}
test(); |
In your test you take the method from the prototype and run it without and instance. function MyClass() {
this.x = 'foobar';
}
MyClass.prototype = {
hello() {
return this.x;
}
};
const instance = new MyClass();
console.log('one:');
console.log(instance.hello());
const helloMethod = instance.hello;
console.log('two:');
console.log(helloMethod()); Will give
You could fix this by binding the instance afterwards const helloMethod = instance.hello.bind(instance);
console.log('two:');
console.log(helloMethod()); |
Yes, you are correct, that's what we'd expect for any class. However, this was not the case (somehow) on v7. It's also not the case for I'm assuming the change is now methods are inherited from the prototype when previously they were directly assigned to the document object? I can see the performance gain in that, if it's the case, so losing this and having to explicitly bind methods would be reasonable in exchange. Though something that could be done is to set a getter for each method in the prototype such as: get methodName() {
return originalMethodFn.bind(this);
} This would allow us to have the same performance gain when creating documents as the getter would be part of the prototype, and would only fire when accessed. At the same time, accessing that property would return the original method bind. Following your example: function hello() {
return this.x;
}
function MyClass() {
this.x = 'foobar';
}
MyClass.prototype = {
get hello() {
return hello.bind(this);
}
};
const instance = new MyClass();
const helloMethod = instance.hello;
console.log('one:', instance.hello());
console.log('two:', helloMethod());
It would need to be done programmatically, so something like: const methodGetters = Object.entries(methodsObj).reduce(
(acc, [key, method]) => {
acc[key] = {
get() {
return method.bind(this);
},
enumerable: true
};
return acc;
},
{}
);
Object.defineProperties(RxDocumentPrototype, methodGetters); Applied to your example: const methodsObj = {
hello() {
return this.x;
}
};
function MyClass() {
this.x = 'foobar';
}
const methodGetters = Object.entries(methodsObj).reduce(
(acc, [key, method]) => {
acc[key] = {
get() {
return method.bind(this);
},
enumerable: true
};
return acc;
},
{}
);
Object.defineProperties(MyClass.prototype, methodGetters);
const instance = new MyClass();
const helloMethod = instance.hello;
console.log('one:', instance.hello());
console.log('two:', helloMethod()); I'd personally advocate for it being the default, as I can't see any reasonable downside, but otherwise it'd be very useful to have it as an option regardless. |
Yes this does not work in v8 because we set the orm-methods at the You proposal to bind when the getter is called would work, but only for the ORM-methods. Your described use case is legit but this is what the postCreate-hook is for. You could get the same with function setName(){
return this.doSomethingWithThis();
}
myCollection.postCreate(function(plainData, rxDocument){
rxDocument.setName = setName.bind(rxDocument);
}); |
Hi @pubkey , really appreciate the answer. Let's look over your points. Regarding explicit binding via hooks or plugin, doing so was the first thing I tried before opening the issue. Those properties are protected by RxDB and marked as read only, so it'd only be possible to do by monkey patching the functions that do RxDocument creation via plugin. Since those are private, I don't think doing that is a great idea since it could break any time the internals change. You also point out that the "proposal to bind when the getter is called would work, but only for the ORM-methods". I'm not fully understanding your point here - as far as I can see it, it could be applied to any method, both user defined and ORM ones (remove, create, and so on). If we have the user defined const ORMmethods = {
remove() {
// ...
}
// ....
};
const methods = {
hello() {
return this.x;
}
// ....
};
const createGetters = (obj) => Object.entries(obj).reduce(
(acc, [key, method]) => {
acc[key] = {
get() {
return method.bind(this);
},
enumerable: true
};
return acc;
},
{}
);
Object.defineProperties(Document.prototype, createGetters(ORMmethods));
Object.defineProperties(Document.prototype, createGetters(methods)); Last, you mention that it "would be confusing and officially supporting this will bribe people into passing arround functions without using Taking mst as an example, when defining methods (actions), they pass a const collection = await db.collection({
// ...
methods: (selfOrThis) => ({
hello() {
return selfOrThis.name;
}
})
}); Unfortunately, contrary to the I'm personally passing around a lot of methods, and would imagine others are too. Making it so there's a need to think about binding on each single instance you use any method from RxDB (as opposed to any other state library) I'd consider a strong hassle and a very pronounced downside to the library as a whole. I'd urge to consider that there's a chance that by not wanting to introduce complexity to the library, we might actually be doing so to a higher degree. To recap, whether it is via getters or via closure + ORM methods bind by default, I'd put forwards there should be a way by which RxDB allows us to not have to explicitly bind methods on usage. I'm not sure how other libraries pass around methods, but in the case of React, I can assure you this will be a very relevant point for many people using RxDB with it. |
I am convinced. In your example-code you set |
That's great news!! Really glad about this -really, you have no idea. All right, regarding Getters, when set on object definition, get both As an example, given: const obj1 = {
get some() {}
};
const obj2 = {};
Object.defineProperties(obj2, {
some: {
get() {}
}
}); If we did If we were to do
However, with const obj = {};
Object.defineProperties(obj, {
some: {
get() {},
enumerable: true
}
});
console.log(Object.keys(obj));
console.log(Object.getOwnPropertyDescriptors(obj));
All in all, I was just setting |
Ok. I initaly set So I think I can close this. Next beta-release will come when travis has run throught. |
Sounds great! |
Hi @pubkey , I think there's a case for What do you think? |
Great observation. Changed, now looks like this |
Great! |
Document methods are not hard bind to the document on 8.0.0-beta.7, so if:
Then:
This is not the case for statics, nor was it for v7.
The text was updated successfully, but these errors were encountered: