stingerlabs / ember-graph

Ember persistence library for complex object graphs
http://stingerlabs.github.io/ember-graph/
MIT License
76 stars 5 forks source link

[Feature] Embedded objects #51

Open bakura10 opened 10 years ago

bakura10 commented 10 years ago

Hi,

As discussed on the forum, this is a more formal proposal for the embedded objects (temporary name). I'll try to implement this as an exercise ;) (note: I didn't use ember-graph yet, so sorry if my terminology is wrong)

What is an embedded object?

An embedded object is an object that always comes embedded into another, as it is not saved on its own. This is a feature by several server-side ORM to reduce code duplication, separating concerns or for optimization purpose. It can be seen as a kind of document oriented feature.

As a consequence, an embedded object cannot be directly queried, saved or destroyed on its own. It cannot be stored in an identity map as an embedded object does not have concept of identifier.

For now, we assume that the embedded feature is restricted to OneToOne only (this is what most server-side ORM do, anyway).

Syntax

I suggest to reuse the name "embedded". This is the terminology that is used in Doctrine 2 (biggest PHP ORM) and Hibernate (Java ORM). In Ember-Data it is very confusing because they use the "embedded" word for the concept of embedding side loaded entities.

Can you please confirm me that the word "embedded" is not used for that in Ember-Graph too?

Definition of an embeddable

An embeddable object is created using the Embeddable object:

App.City = EG.Embeddable.extend({
    // Properties
})

Note that an embeddable object CANNOT have association (as this is often the case in ORM). And it will simplify implementation, I suppose. So this is not valid:

App.City = EG.Embeddable.extend({
    foo: EG.hasMany()
})

Only attributes are allowed inside an embedded object.

Using embeddable

An embeddable object can be added into a traditional model using a new keyword: embeds:

App.Address = EG.Embeddable.extend({
    city: EG.embeds('city')
})

address.set('city', App.City.create());

Must-have

It must be capable to properly serialize, deserialize, track changes.

gordonkristan commented 10 years ago

What I'm thinking currently is I'll create a function that will generate an AttributeType for you. Then, you can use it like any other model attribute type. You would tell it which fields to generate, and it would create an embedded object type. For instance, let's say I wanted a User model to look like this:

{
    "name": "Ben Derisgreat",
    "address": {
        "city": "New New York",
        "state": "NY",
        "zip": 100222,
        "street": "294 W 57th Street"
    }
}

I would declare the model like this:

App.User = EG.Model.extend({
    name: EG.attr('string'),
    address: EG.attr('address')
});

App.AddressType = EG.generateEmbeddedType({
    fields: ['city', 'state', 'zip', 'street'],
    defaultValues: {
        city: '',
        state: '',
        zip: null,
        street: ''
    }
});

The function would automatically generate the serialize, deserialize and isEqual functions for you. Then, address would act just like any other attribute. You could modify its properties, observe changes, and persist the changes.

I would also create a helper that would generate an array type. It would look very similar to above, only it would have an array of those objects embedded. The array, just like above, would be observable and modifiable just like any other Ember array.

I would also allow the serialize, deserialize and isEqual methods to be overridden if the user wanted to customize them.

Some limitations:

  1. I don't really want to generalize the case of nesting arbitrarily deep. I think this would only deal with items at the top level. If you wanted to keep nesting objects, you would have to override the 3 methods above.
  2. You can't have relationships in these nested objects (which it seems you're OK with). Relationships are meant to go between records, not specific properties of records. If you find yourself needing relationships for embedded records, that record probably shouldn't be embedded.
  3. Creating an object of this type won't be like creating a model. The syntax will likely be something like:

      user.set('address', App.AddressType.newInstance());

I would also likely come up with a better name for them, since 'embedded' seems to mean a few different things already.

Does this feel like it would satisfy your use case?

bakura10 commented 10 years ago

I agree with the two first limitations, those limitations also exist in my ORM, so that's not a problem.

The only issue I see with the "attr" is that it prevents using the reserved names (string, number, boolean...). While not a big problem, it can leads to very hard to debug bugs. That's why I suggested to have a different thing.

gordonkristan commented 10 years ago

There's not really a way around being able to use reserved words. Whether it's considered an 'attribute' or not, it still has to jive well with the Ember object model. For instance, no matter what kind of property it is, naming it content would just be very stupid, since Ember uses that property name internally a lot.

Reserved names are definitely going to happen, but I don't think they're much of a hinderance. Plus, Ember-Graph alerts you at runtime if you've used a reserved name for an attribute or relationship.

EDIT: Also, string, number and boolean are perfectly valid property names (although dumb ones). The only reserved names right now are: id, type, content, length and model.

gordonkristan commented 10 years ago

I don't have a whole lot of time these days, but I managed to get this started. Hopefully within a week or so I'll have something usable. Right now, the syntax is:

App.Person = EmberGraph.Model.extend({
    name: EmberGraph.embeddedHasOne('name')
});

App.Name = EmberGraph.EmbeddedModel.extend({
    first: EmberGraph.attr('string'),
    last: EmberGraph.attr('string')
});

My focus right now is on guides on documentation, I just happened to get this started with an idea at work. But at least you know that support is coming.

bakura10 commented 10 years ago

Awesome Gordon. ! That definitely sounds like the best way to solve this problem!

Envoyé de mon iPhone

Le 26 août 2014 à 23:48, Gordon Kristan notifications@github.com a écrit :

I don't have a whole lot of time these days, but I managed to get this started. Hopefully within a week or so I'll have something usable. Right now, the syntax is:

App.Person = EmberGraph.Model.extend({ name: EmberGraph.embeddedHasOne('name') });

App.Name = EmberGraph.EmbeddedModel.extend({ first: EmberGraph.attr('string'), last: EmberGraph.attr('string') }); My focus right now is on guides on documentation, I just happened to get this started with an idea at work. But at least you know that support is coming.

— Reply to this email directly or view it on GitHub.