Ember-Graph - An Ember-Data alternative

I thought that might be the case. Ember-Data loosely follows the JSON API, but Ember-Graph follows it pretty rigorously by default. You can read about relationships here. What’s happening is that the serializer expects all of your relationships to be under the links object. Something like this:

{
    "cities": [{
        "id": "NY",
        "name": "New York",
        "links": {
            "libraries": ["lib1", "lib2"]
        }
    }]
}

If you’d prefer to not use the links object, you can get around that pretty easily. I would override deserializeRecord:

App.ApplicationSerializer = EG.JSONSerializer.extend({
    deserializeRecord: function(model, json) {
        // Put relationships in a `links` object like the serializer wants
        json.links = {};
        model.eachRelationship(function(name) {
            json.links[name] = json[name];
            delete json[name];
        });

        // Then let the serializer do the rest of the work
        return this._super(model, json);
    }
});

Since that seems to be such a common format, maybe I’ll build-in support for it using an option.

EDIT: Also, you’re not bothering me at all. Every suggestion or bug you have is very helpful to me. I want EG to be the best library it can be, and I certainly can’t do it alone.

So, technically you can not omit required attributes when persisting them. But you can create a record which does not have all required fields set. You just fulfil the requirements later.

So, coincidentally, the bug I have in my application is because I lack something like this. It looks like it might be added a bit sooner than I thought. :smile:

Last night I managed to implement the functionality I think you desire. 3 main changes happened.

  1. Records do not have to be created all at once. You may create a record and not initialize any attributes or relationships, even if they’re required. You may also modify read only properties as long as the record hasn’t been saved yet.
  2. Before saving, the store makes sure that all of your attributes and relationships have been initialized. It won’t let you proceed without initializing them. (Even the optional ones. You’ll have to initialize them yourself.)
  3. I added a serverOnly option for declaring relationships and attributes. This bypasses #2 and makes the property read only.

Here’s a quick example showing the functionality.

App.Post =  EG.Model.extend({
    title: EG.attr({ type: 'string', readOnly: true }),
    posted: EG.attr({ type: 'date', serverOnly: true })
});

var post = store.createRecord('post'); // no data required yet
post.get('title'); // undefined
post.get('posted'); // undefined
post.save(); // throws error, `title` not initialized
post.set('title', 'Hello World'); // works as excepted, read only doesn't apply to new records
post.set('posted', new Date()); // throws error, property is server only
post.save();
...
post.get('posted'); // Tue Aug 05 2014 10:44:58 GMT-0400 (EDT)
post.set('title', ''); // throws error, read only
post.set('posted', new Date()); // throws error, read only

Gordon, this project shows a lot of promise. When I get a little more up to speed, I would like to try migrating

But before I get too excited, i need to ask: from a beginners perspective, I have learned a lot about Ember-Data, but I have not been able to use any of that knowledge yet, because my APIs do not follow JSON-API, I am pretty sure mine are formatted with JSEND spec. Ember-Data instantly turned into an ugly beast because I could not conform to their API convention… I use Ember-Model instead, which has a few shortcomings as well…

  • How much of my Ember-Data knowledge can translate over to Ember-Graph?

-and-

  • How much work would it take to write custom Adapter/Serializer to accomodate JSEND APIs, compared to Ember-Data (assuming you have any knowledge about it)

-or-

  • is there somewhere can I turn to learn (beginner style) writing a custom adapter/serializer?

First I’d like to say thanks. I have a lot of fun working on this library, so it’s nice to hear that it’s not a complete waste. :smile:

On to your questions. Quite a bit of Ember-Data knowledge will transfer over. Ember-Graph was meant to copy Ember-Dat at a high level. That is, most of the design decisions where chosen because I wanted them to resemble Ember-Data. Ember-Graph has the same basic parts as Ember-Data: stores, serializers, adapters, models and attribute types (transforms). Much of the API is also similar. Extending models, creating records, performing updates, etc. are all done in a very similar manner to Ember-Data. There are of course quite a few differences to support the features, but the ideas are all there. If you’re comfortable with Ember-Data, you’ll be comfortable with Ember-Graph.

Secondly, as far as APIs go, I know what it can be like to have a non-conforming API. My current application conforms quite well to the JSON API, but my previous application didn’t. At the time, when I was first starting Ember, writing a new adapter (or serializer) was hard. There was no documentation and hardly any examples. Unfortunately, that is true of Ember-Graph, but not for long. Within the next week or two, I want to have guides to get up and running with Ember-Graph, including writing a custom serializer or adapter.

So as of now, creating an adapter or serializer will be similar to Ember-Data (although a bit easier). But I’m going to write a guide to make it easy very soon. And, if you need help in the meantime, I would be more than happy to help. Just message me or create and issue on Github.

Most importantly, I would like to comment on this:

Ember-Data instantly turned into an ugly beast because I could not conform to their API convention

This certainly won’t happen with Ember-Graph. Your serializer or adapter may be a bit ugly, but if your data model can be represented as a directed graph, Ember-Graph will accommodate it perfectly. Any time I find an API abstraction leaking through to my application code, I generally re-work Ember-Graph a bit to eliminate the need to do that. If I’ve done my job correctly, your API shouldn’t matter when it comes to using Ember-Graph.

Hm, I guess it is time for a little disclaimer: I’ve spent most of the last year mainly writing backend java services, based on java and groovy, so probably I am used to a few conventions that don’t seem that natural to others.

But first off, thanks a lot for the update. Much appreciated.

Records do not have to be created all at once. You may create a record and not initialize any attributes or relationships, even if they’re required. You may also modify read only properties as long as the record hasn’t been saved yet.

Awesome :smiley_cat:

Before saving, the store makes sure that all of your attributes and relationships have been initialized. It won’t let you proceed without initializing them. (Even the optional ones. You’ll have to initialize them yourself.)

That probably is the point where some of my java background kicks in. In java we have the concept of null, which essentially means that nothing is there and it has not been set to specific value. If you create an object and no specific value has been assigned, all of the variables are unset (null) (at least for instance variables). In case anything is optional (for instance when persisting to a RMDB), it is fine to leave the optional values unset (null). To me this concept has been working exceptionally well and I actually can’t remember any situation where I have been wishing it would be different. This is different from languages like c++ where an unset variable actually might not have a defined value, which usually sooner or later causes somebody to forget to initialise a variable and someone else is wondering why the application crashes. Actually I don’t want to say that ember graph should do it this or that way, I am just wondering why not do it the java way, as this is something that has proven to work very well to me: Create an object, set the required values, save it - all done

I added a serverOnly option for declaring relationships and attributes. This bypasses #2 and makes the property read only.

I think this is great. For instance for things like a created date, which is assigned by the backend. I use MongoDB as a datastore and it already persists the current time in its generated id field. So I never allow the client to set that value and I never explicitly persist a created date, I just pull it out of the id. serverOnly seems perfect for that. I am not quite sure about the relationship thing here yet, I will check it out.

small typo here on line 461:

https://github.com/gordonkristan/ember-graph/blob/master/src/serializer/json.js#L461

I guess that should be:

return { name: name, value: meta.defaultValue };

Would you prefere pull requests for things like that?

So for attributes with default values, would you like the value to be set to the default as soon as the record is created, or when the record is saved?

App.Email = EG.Model.extend({
    subject: EG.attr({
        type: 'string',
        defaultValue: '(No Subject)'
    })
});

var email = store.createRecord('email');
email.get('subject'); // 'No Subject' or undefined
email.save();

I left it the way it is because I wasn’t sure what to do in this scenario. The first way seems correct, but it doesn’t allow somebody to explicitly leave the value as undefined. Then again, Java doesn’t allow you to do that either, so maybe I should go with the first option. Any thoughts?

Would you prefere pull requests for things like that?

Yes, pull requests are more than welcome. :slight_smile: I already took care of this one though.

Hm, that’s actually not an easy question. If you think about the store in the way of a database and the model as the definition for your database (and the backend), I would prefer to have the default value apply upon persisting the record.

However, one might also argue that if you create a new record and display it to the user in order to enter its values. Than it might be handy if a default value would already be set. For instance a boolean that should be true per default would already mark the checkbox.

I just realise that I probably must be careful that my work as a backend developer might gain to much influence on such decisions…

I think the second option would probably be better for the use cases ember is trying to solve.

 I already took care of this one though.

Doesn’t matter. I’ll find more :smiley:

Hi,

This definitely looks like a nice solution. Just a few questions for features that always make Ember-Data very hard to use for my cases:

  1. Do you support embedded models (embedded objects whose persistence is controlled by the parent object). This is provided in Ember-Data through a third party library (GitHub - adopted-ember-addons/ember-data-model-fragments: Ember Data addon to support nested JSON documents) but this seems such an often used pattern I’d like to have it by default.

  2. One thing I’ve always found strange with Ember-Data is that you cannot “fork” a model. When you edit a model, all the bounded properties are also updated, which leads to very strange UX. Is there such a feature in Ember-Graph ?

  3. Does it support nested URLs?

Thanks :smiley: (sorry if you already answered this, but couldn’t find it in the doc!)

Regarding embedded models, I have been asking the same question. This was the response labeled as 4.

Ha sorry, I should have read it more carefully. @gordon_kristan, if you need more details about the typical use cases of embedded objects (sorry, let’s say “unmanaged objects”, it’s always confusing as in Ember-Data terminology “embedded object” actually has an identifier), let me know :).

  1. As Hummingbird mentioned, I briefly touched on this before, but I’m glad you linked to that repository. It gives me a little more information to go on. I’ll probably write a piece on this soon, but for now, I don’t think I’ll every support ‘embedded records’. The reason is that I don’t think you’re modeling your data correctly (in a graph-like manner) if you have records inside other records. You can either have attributes on a record (which can be of any type, like an object array), or you can link records together using relationships.

    I think it depends on the use case, but I see two types of ‘embedded records’

    • The embedded record has no unique identifier. This means that the ‘record’ should actually be an attribute. For these cases, I hope to include attribute types in Ember-Graph that can handle that data gracefully. (Possibly extending the JSON API a bit.)
    • The embedded records have unique identifiers. This means that the ‘embedded’ records can actually be separate records linked by a relationship. I think flattening your model (at least on the client side) is the best solution to this problem. I have plans on the roadmap to one day include a mixin for serializers that will separate embedded records into separate records, leaving your data model on the server the same, but flattening it on the client.
  2. I’m afraid there is no forking models (yet). To be honest, I’ve had use cases for this feature, but I’ve never put a lot of thought into it. It’s certainly an interesting idea to say the least. EPF supports forked models, so that might be an option for you. Given the current roadmap, you probably wouldn’t see this in Ember-Graph for many months.

  3. Do you mean nested URLs for REST resources? Not out of the box, no, but the RESTAdapter is fairly easy to extend. If you have a specific use case in mind, and it seems reasonable, I’m always happy to add features into the main branch.

And don’t worry about asking, you won’t find much in the documentation now. After we release the first version of our app this week, I hope to find some time to go back and update it with all of the changes from the last few weeks.

One quick question. In the adapter on updateRecord: If the server does not return the updated entity, what would I need to return in the then() to make ember-graph happy?

Hmm, never really thought about that. Up until this point I’ve been returning the updated record. I think this falls in with issue #47. I’ll try to get to that tonight or tomorrow.

In the meantime, if I had get it working as is, I’d probably do something like this:

updateRecord: (record) {
    return this._super(record).then(function() {
        // Get the current state of the object
        var fullJson = this.serialize(record, { requestType: 'createRecord'});
        // Add the ID to it
        fullJson.id = record.get('id');
        // Give it back to the store as if the server returned it
        var payload = {};
        payload[record.get('typeKey')] = [fullJson];
        return payload;
    }.bind(this));
}

Thanks, that somehow gives me a pretty weird nested object structure in my app. But its not that urgent. I will wait for your input on #47. Would this be true for the delete method as well? Not sure what it currently is trying to deserialise.

All of the calls accept a payload in return, just in case. For instance, you might delete a thread, so the posts get deleted too. It’s not fully documented yet, but Ember-Graph allows you to specify any records that may have been edited or deleted as the side effect of another call. I’m sure it’s still got issues, but I want Ember-Graph (and it’s built in adapters and serializers) to accept data at any chance it can.

Hi @gordon, thanks for your answer. Let me give you some details:

  1. I don’t agree with you on that. In the main PHP ORM, you can find the documentation of so-called “embeddable” (http://doctrine-orm.readthedocs.org/en/latest/tutorials/embeddables.html). They really allow you to decouple your code, where it does not make sense to have a separate table (either for performance issue or for avoiding too many tables).

For instance, you could have an IpAddress, where you may want to store the IP and the subnet mask. Of course, you could create a separate table just for storing that but this is overkill. At the same time, you don’t want to duplicate the “ip” and “subnet” in all your models that store an IP address.

Isntead, you create an Embeddable object (sorry, I’m reusing Doctrine 2 terminology here), called IpAddress, where you have two fields. The two fields (ip and subnet) will be stored in the same table, but what you will receive as payload is:

{“id”: 54, “country”: “FR”, “city”: “Paris”, “ip”: {“part”: “123.45.46.45”, “subnet”: “16”}}

As you can see, the embeddable is not persisted on its own, but contained within the same table, but “act” as a separate object.

There are actually quite a lot of cases where it makes a lot of sense! Of course, it can be written with a custom type, but that seems more like a hack rather than an actual solution, because if I design my application using embedded properties like that, I actually want to consider the “Ip” like a separate model, as it was designed like this server-side.

  1. Too bad :(. I know EPF but they do not cover nested URL neither. Just a stupid question: how do you handle such cases? I mean, it seems like really an essential feature, and I’ve seen a lot of cases where updating an input field just destroyed completely the whole UI by changing all the other fields in real-time in the interface, so that the user may think that it is saved in real time, where it is actually not. Do you modify your design so that bound properties of a model are never shown together with a form?

  2. Yep. Nested URLs is another thing I don’t know how you can live without :D. It’s by far the most frustrating part in Ember-Data. A discussion has been open for that several months ago but it seems there is no news (Nested URLs for Ember Data's RestAdapter proposal).

Use case is… well… nearly all my API is nested. I think the Stripe API is a good example of nested API. For instance, if you want to create a new subscription for a given customer, you POST to “/customers/45/subscriptions”. If you want to get all subscriptions for a given customer, you do GET “/customers/45/subscriptions”.

I honestly do not see any other ways to represent that kind of things. Of course you could GET “/subscriptions?customer=45” but that’s not correct imho. The subscriptions belongs to the customers, so it’s not “filtering by customer”, but rather getting subscriptions for customer.

Most of the time, a nested of one level (“/foo/:foo-id/bar”) is enough, because if you nest more (“/customers/45/subscriptions/56/invoices”) can be rewritten “/subscriptions/56/invoices”.

There are a lot of proposal syntaxes in the mentioned thread, don’t hesitate to have a look at it :).

Sorry for all the feedbacks and complaints, I’d love to help but JS is really not my best language. But I’ve worked and built a lot of API and I feel frustrated not finding a library that covers all the use cases =).

I may give a try to the “embedded”/“fragment” thing though, if you’re interested. Could be a nice way to make me better at JS :wink:

I still think you’re describing an attribute though. To me, a record is a lone entity. It has an ID, it can be persisted, deleted, etc. But maybe we’re just using the wrong terminology. Let me give you an example of how I would declare that model and attribute, and you can tell me what you don’t like about it. Let’s assume that we have a User model that contains an IPAddress.

App.User = EG.Model.extend({
	ip: EG.attr({
		type: 'IPAdress'
		defaultValue: Em.Object.create({ part: '0.0.0.0', subnet: 0 })
	})
});

App.IPAddressType = EG.AttributeType.extend({
	serialize: function(ip) {
		return {
			part: ip.get('part'),
			subnet: ip.get('subnet')
		};
	},

	deserialize: function(json) {
		return Em.Object.create({
			part: json.part,
			subnet: json.subnet
		});
	},

	isEqual: function(a, b) {
		return (a.get('part') === b.get('part') && a.get('subnet') === b.get('subnet'));
	}
});

// ...

var user = store.getRecord('user', '54');
console.log(user.get('ip.part')); // 123.45.46.45
console.log(user.get('ip.subnet')); // 16
user.set('ip.subnet', 24); // user.ip.subnet is now 4
user.save(); // EG detects the change and persists the new value

As you can see, you’re working with the value as if it were part of the class, exactly like the User/Address example in the Doctrine documentation. You can even add observers to the properties of the object.

As far as forked records go, there’s nothing in Ember-Graph for that yet. I usually use what I call a proxy. It’s similar to Ember’s ObjectProxy, but it captures changes and doesn’t apply them to the model. Then, when I want to save the changes to the model, I call apply() on the proxy and it writes all of the pending changes to the model. Either way, this is certainly an issue I’ve seen crop up multiple times, I just don’t have any first class support for it yet. (Although it would certainly be nice to have.)

Finally, with nested URLs, I think I could certainly implement the proposed syntax in the thread you linked to. I would just have to be sure that it’s covering the actual use cases before I put it in. Personally, I think having an expanded buildURL function could solve many of the use cases. I’ll look into it a bit more, along with the Stripe API you proposed.