thinky
thinky copied to clipboard
Support for embeddable objects
Hi Neumino,
I am playing with thinky and when it comes to relationship, the assumption is to leverage the join feature of rethinkdb which is great. However in some scenarios, it's preferable to embed rather than to join.
To some degrees, thinky might already support this with nested objects but it forces you to inline all your embeddables and I am not so sure it's ideal to support collection of elements.
Maybe if we could declare our embeddables with thinky.createEmbeddable and then reference them in the schema where they are embedded.
Now i might be biased by my prior JPA experience and maybe this whole thing is unnecessary when it comes to json documents...
What do you think ?
I'm not really experienced with JPA, and I'm not sure what you mean by embeddables.
Could you give a concrete example of what thinky.createEmbeddable
would do?
For example, If I were to model the example at http://rethinkdb.com/docs/data-modeling/ (Embedded arrays):
var Post = thinky.createEmbeddable({ title: String, content, String });
var Author = thinky.createModel("Author",{ id: String, name: String, posts: [Post] });
var author = new Author({ name: "William Adama" });
var post = new Post({ title: "Decommissioning speech", content: "The Cylon War is long over..." });
author.posts.push(post); author.save();
The idea is to end up with one table only with the following structure:
{ "id": "7644aaf2-9928-4231-aa68-4e65e31bf219", "name": "William Adama", "posts": [ {"title": "Decommissioning speech", "content": "The Cylon War is long over..."} ] }
Sorry for the delay, this issue somehow went through the cracks
var Post = thinky.createEmbeddable({
title: String,
content, String
});
var Author = thinky.createModel("Author",{
id: String,
name: String,
});
Author.hasMany(Post, "posts", "id", "idAuthor");
The syntax for embeddable are basically sugar for hasOne
/belongsTo
and hasMany
.
I'm not a big fan of this syntax because:
- It feels like the embedded document is stored in its parent document when it is not -- I could do it and add sugar for pivot operations, but this will lead to something insane (and not super efficient I think).
- It is somehow ambiguous on what the relation is (hasMany or hasAndBelongsToMany), and where the foreign keys are (hasOne vs belongsTo)
Adding the syntax below for hasOne
and hasMany
shouldn't be hard though, so I'm not strongly opposed to adding it.
I'm just worried that it will later require some users to migrate their relations (which currently can't automatically be done).
var Author = thinky.createModel("Author",{
id: String,
name: String,
posts: Post // or posts: [Post]
});
Thanks for getting back to me. I am just trying to find out how thinky should let users embed objects if they choose/need to. Joins are great dont get me wrong, but in some cases, you will prefer locality on your data over joins and it would be great if Thinky could support this.
I am certainly not the right guy to ask for how the api should look like as I am new to JS/Node and dont have enough experience to propose something that would feel natural and how JS/Node people would expect it to work.
If we indeed could support something as simple as posts: [Post] , that would be great, feels natural, intuitive and do express that the posts will be embedded in the Author imho.
@Tlvenn Reading your comments, I think that what you really want is a sub document, which thinky does support, though it's called "nested fields" in the documentation.
As the createModels docs mention, you can have nested documents:
var User = thinky.createModel("User", {
id: String,
contact: {
email: String,
phone: String
},
age: Number
});
So, your example is currently do-able in thinky as:
var Post = {
title: String,
content: String
};
var Author = thinky.createModel("Author", {
id: String,
name: String,
posts: [Post]
});
var author = new Author({
name: "William Adama"
});
var post = new Post({
title: "Decommissioning speech",
content: "The Cylon War is long over..."
});
author.posts.push(post);
author.save();
This would output:
{
"id": "7644aaf2-9928-4231-aa68-4e65e31bf219",
"name": "William Adama",
"posts": [
{"title": "Decommissioning speech", "content": "The Cylon War is long over..."}
]
}
Unless I'm missing something, this covers your use case.
Sorry for the late reply Mogul. Indeed this approach would work, the only thing I don't like is that I can't express anything like contraints/validation/formula on the embedded class given it is a pure JS object. Ideally I should be able to attach almost as much as if it was a thinky model.
@Tlvenn you can, but the docs do not make it obvious. I am doing this right now:
var User = thinky.createModel("User", {
id: String,
contact: {
email: {
_type: String,
enforce_type: 'strict',
validator: validator.isEmail
},
phone: String
}
});
You can express computed fields with type: 'virtual'
. Relation constrains are at least partially implicit in how you embed it: field: Subtype
is a has-one, field: [Subtype]
is a has-many.
+1
I think the ability to define virtual fields which operate just on the nested model properties would be great. e.g:
var User = thinky.createModel("User", {
id: String,
contact: {
email: String,
phone: String,
fullContact: type.virtual().default(function() {
return this.email+" "+this.phone;
}
}
});
@cur3n4 -- you can already do that.
@neumino Indeed! However it doesn't seem to work for arrays of nested objects. I get a TypeError: Cannot read property '0' of undefined.
Ideally defining methods on the nested model (not only virtual) would be very useful.
@cur3n4 -- can you open a new issue with the full backtrace and a snippet to reproduce this error? Thanks
@neumino Done: #375
From my point of view, the main thing I am missing about nested models is the ability to define methods (not virtual properties). Something like this:
var User = thinky.createModel("User", {
id: String,
contact: {
email: String,
someMethod: type.method().default(function(someParameter1, someParameter2) {
// More complex logic than this...
return this.email == someParameter1 || this.email == someParameter2;
}
}
});