thinky icon indicating copy to clipboard operation
thinky copied to clipboard

Support for embeddable objects

Open tlvenn opened this issue 10 years ago • 13 comments

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 ?

tlvenn avatar May 21 '14 04:05 tlvenn

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?

neumino avatar May 21 '14 19:05 neumino

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..."} ] }

tlvenn avatar May 22 '14 03:05 tlvenn

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]
});

neumino avatar Jun 30 '14 15:06 neumino

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 avatar Jul 01 '14 06:07 tlvenn

@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.

Morgul avatar Jul 24 '14 23:07 Morgul

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 avatar Aug 18 '14 01:08 tlvenn

@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.

simonratner avatar Sep 05 '14 19:09 simonratner

+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 avatar Oct 26 '15 12:10 cur3n4

@cur3n4 -- you can already do that.

neumino avatar Oct 26 '15 16:10 neumino

@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 avatar Oct 26 '15 22:10 cur3n4

@cur3n4 -- can you open a new issue with the full backtrace and a snippet to reproduce this error? Thanks

neumino avatar Oct 27 '15 01:10 neumino

@neumino Done: #375

cur3n4 avatar Oct 28 '15 23:10 cur3n4

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;
    }
  }
});

cur3n4 avatar Nov 06 '15 00:11 cur3n4