The project I am working on relies heavily on many-to-many relationships, however I am unable to see how exactly to achieve this using Ember Data in Ember JS.
The use case I currently have is made up three models:
Person
Address
PersonAddress
The link between the person is necessary for what I am currently working on. Would it be possible to get an example of how this could be achieved?
Apparently you are trying to manage the many-to-many relationship yourself using the intermediate PersonAddress model, which I assume holds a single person associated with a single address.
This is not a very good way to do things. To find a person's addresses, you're going to have to manually lookup all the entries in PersonAddress for that person, then take the addresses from those entries. The problem with this approach, among others, is that when a new address is added, for example, you will have to redo this lookup, and ensure that it happens automatically in order for the UI to be updated properly.
It is much better to simply have hasMany relationships on both Person and Address. Then Ember Data will keep everything in sync--something like
// models/person.js
export default DS.Model.extend({
name: DS.attr(),
addresses: DS.hasMany('address')
});
// models/address.js
export default DS.Model.extend({
address: DS.attr(),
persons: DS.hasMany('person')
});
Related
Say suppose I have a people's template (people.hbs), and I want to list all the people that is retrieved from a GET to http://backendserver/api/people.
However in future I decided to implement something that requires me to sort these people based on the data that I get from http://backendserver/api/vehicle_people. In this data there is only the people id and the vehicle id so I can't just connect people and its associated vehicle information together , I also get vehicle data from http://backendserver/api/vehicles.
Some of the confusion on how to implement this in Ember2 is
Should I put all this info inside routers/people.js
something like this
export default Ember.Route.extend({
model(){
return Ember.RSVP.hash({
people_with_vehicle: //find from this.store all the people and the vehicle ,
people_with_no_vehicle:,
all people:
})
}
How should I load the data for http://backendserver/api/vehicle_people if I don't want to create any specific route or template ?. In ember2 it does like an automatic load based on the route you go to e.g if you defined in your /adapters/application.js
export default DS.RESTAdapter.extend({
namespace: 'api',
host:'http://backendserver/'
});
Your "vehicle_people" endpoint is best mapped to an Ember Data model called VehiclePeople, which would be:
DS.Model.extend({
person: DS.belongsTo('person'),
vehicle: DS.belongsTo('vehicle')
})
There are other approaches, but let us map, at the model level, which vehicles people drive. First we establish the relationship between people and your little two-attribute people-to-vehicle map called vehicle_people:
vehiclePeople: DS.hasMany('vehicle_people', { async: true })
This will send a network request, or network requests, to the vehicle_people endpoints, based on the person's ID, whenever this field is accessed. We'll see an alternative way to handle that in a minute.
Now a computed property serves to gives us a list of the actual vehicles.
vehicles: Ember.computed.mapBy('vehiclePeople', 'vehicle')
and
hasVehicle: Ember.computed.bool('vehicles.length', 0)
Creating lists of people with or without vehicles is not a matter of the model; it's pre-calculation that you almost certainly want to do in your controller:
Ember.Controller.extend({
peopleWithVehicles: Ember.computed.filterBy('model', 'hasVehicle')
and so on.
If you want to preload the vehicle_people relationships, so Ember Data does not attempt to go out and get them one at a time, the best way is to do that in the beforeModel hook:
...route
beforeModel() { return this.store.find('vehicle_people'); }
Is this the right way to normalize the model?
Having the little model-but-not-a-model which does nothing more than contains mapping from one model (people) to another (vehicles) is probably not the right way to organize your models and endpoints. You would be much, much better off simply include vehicles directly in person. What's the roadblock to doing that?
Your route is nothing to do with your model, in terms of naming convention, although you would probably often have them the same, and nest your routes to create your UI.
Ember does not do any magical matching of your route name to the api endpoint name. Ember Data does a little name magic to match the API endpoint to the model in the store.
1.
I assume these would all be people, and that they would have an attribute which defines whther they are vehicle people or non vehicle people. You can also include associated models in a single API endpoint, if it makes sense.
Include the model on the route that matches the template you wish to use that data on, regardless of what the route is called.
I dont understand how ember loads related models.
Lets say thats my model:
export default DS.Model.extend({
title: DS.attr('string'),
description: DS.attr('string'),
states: DS.hasMany('state', {async: true})
})
I load this on of my outer routes. When navigating though an ember-app (into nested routes), model-contexts are often provided for routes not by the model-hook of the route but with the link-to helper (when using dynamic-segments, the model-hook will be ignored). When the target route has something in its template like {{#each model.states as |state|}}, ember will load automatically the related model-entrys from (in that case)the state-model. (How and why? - Just because of the each in the template?
When accesing an dynamic-route directly, the model is not given and the model-hook of the dynamic route will be called. So loading my model is easy: just override the model hook and load record with the url parameter (return this.store.find('item', {title: params.item_title})). But no related models will be loaded. How can I do that manually and what (and when) is the way ember do it by default?
How does Ember know to automatically fetch relationships?
ember-data allows you to define relationships (currently only belongsTo and hasMany) with async option set to true or false. Based on this option, after fetching the model from the API (via find method), ember-data will expect relationships object either directly in the response JSON or not. You have async: true (which is rather a common and supported way of handling relationships), therefore ember-data assumes that in your JSON response it gets the ids of states, but not necesseraily the states themselves.
If you define your hasMany as async: true, it always returns promise. It means, that if you make something like this:
this.get("item").get("states")[0]
Will not work, as get("states") will not return an array, but the promise to fetch this array. However, Handlebars are smart (as the get and set methods of Ember) and it can find out what is a promise and wait for it to resolve before using it content. Therefore if your template contains:
{{#each model.states as |state|}}
Handlebars finds out that states is promise, wait for it to resolve, and after resolve they use its content as an array. Very similar behaviour can be found using belongsTo method. Assuming that your item has one state, if you use the code as follows:
this.get("item.state.somePropertyOfState")
Even if you did not fetched the state and currently you don't know what is the somePropertyOfState value, ember get will find out it's a promise and fetch it for you automatically.
How can I manually fetch relationships?
There are couple of ways to do it.
First one is to explicitly fetch them in ember code:
this.get("item.states").then(function(states) {
# now you have fetched the states that the item has, and moreover
# they are accessible in states variable
});
Second, you can let Ember do it automatically for you as I described formerly (e.g. via template).
Third, you can send the relationships with your response using a mechanism called sideload. This will significantly reduce the number of API requests. When you allow ember fetch your relationships, ember is performing one request per one relationship object, which means that if you have ten states that belongs to item, the API will be hit ten times. However, if you sidelod the states while fetching the item, the request will be sent only once. Take a look here to get more info about that.
Sorry for a long post, but I hope that I clarifed a bit.
I am trying to add a "profile management" functionality to my Ember application, using ember-data. For that, my backend / frontend play together in the following way:
The backend returns a profile array with several "profile objects", with well-known IDs, for the logged-in user, in a fixed URL: /api/profile. Currently I have a me object (settings related to currently logged in user) and a my-company object (settings related to the company belonging to the current logged-in user). It is straightforward to add more objects. The objects are not of the same kind, and have different properties. There is only one object of each kind.
I can view and edit each of the objects using different router/controller/template. Most of the functionality is reusable. For the routers and controllers the only thing I need to adapt is the id of the object in the profile array, and the route to the edit controller.
By using this approach, I can easily add objects to the profile, and add templates to display and edit the properties of each object. The only thing I need to do is:
Define the model for the new object
Setup standard routers and controllers based on the already coded mixins
Create the templates
So this is very easy and flexible, which was my motivation to implement this, but I have one worry. Since I am putting together all properties in the Profile model:
App.User = Ember.Mixin.create({
email : DS.attr('string')
});
App.Company = Ember.Mixin.create({
vat : DS.attr('string')
});
// To do this, the profile model includes the User and Company mixins,
// as well as some common properties.
// TODO: Any other way to do this?
App.Profile = DS.Model.extend(App.User, App.Company, {
name : DS.attr('string'),
type : DS.attr('string')
});
I am worried that when writing back to the server (currently I have only tried with FIXTURES) the properties from the Company object will leak to the User object, and vice versa.
So, the question is: if a model has undefined properties (that is, the data coming from the server does not have those properties) will those properties be sent back to server with null value, when serializing the object, or will they not be set at all in the JSON? Mind you that the properties were not there to start with, but the model has "all possible properties, for all different profile objects".
Maybe there is another way of defining the Profile model, without including all properties of all different objects? I have not been able to do this: if a property is not declared in the model, it can not be used in the template/controller (which is the whole point of defining them in the model, I suppose).
I would also like to hear some generic feedback on the chosen approach. I feel I am bending Ember a bit too much by having all this "bunch of different objects" in a common URL, but I also have the impression that this can reduce the complexity of this part of my application.
A demo can be seen here.
First and foremost i have not worked with ember-data, but i am pretty sure that properties that are not defined are also sent to the server. I also think, that this is not a good approach on data modeling. I pretty sure you should rather have subclasses of your App.Profile. I would go for something like this:
App.Profile = DS.Model.extend({
name : DS.attr('string'),
type : DS.attr('string')
});
App.User = App.Profile.extend({
email : DS.attr('string'),
type : DS.attr('string', {defaultValue : 'user'})
});
App.Company = App.Profile.extend({
vat : DS.attr('string', {defaultValue : 'company'})
});
I guess you can could have one API-Point for all with this approach also, by just defining an API-Endpoint on the level of App.Profile.
We are using Ember together with Ember-Data and are stumped by following setup:
There is a parent object (Epic) which has a number of children (UserStory). We have modelled this accordingly using ember-data:
App.Epic = DS.Model.extend({
name: DS.attr("string"),
description: DS.attr("string"),
project: DS.belongsTo("App.Project")
});
App.UserStory = DS.Model.extend({
name: DS.attr("string"),
description: DS.attr("string"),
goal: DS.attr("string"),
benefit: DS.attr("string"),
epic: DS.belongsTo("App.Epic")
});
What we would like to achieve now, is that we a have a list of independent forms to edit the Epic inline with all of its UserStories. Obvisouly we would simply deliver all of the UserStories together with the Epic via the RESTAdapter. Yet we are afraid that this would lead to ugly update scenarios: changing one UserStory would trigger a update of the entire Epic with all of its UserStories. In our architecture a UserStory is an indepenent entity which should be maintained by a dedidacted REST service.
As an ember-newbie we would like to implement something in the lines of:
Load the Epic via ember-data
Extend the EpicController to load all UserStories into a separate model-(list).
Changes to the Epic via the dedicated form fire a change to the Epic Rest Service.
Magic: Changes to an individual UserStory form fire an isolated and individual change to the User Story Rest Service.
How can the magic be achieved?
Take a look at the controllerFor method:
http://emberjs.com/guides/routing/setting-up-a-controller/
this is kind of embarrassing, but what I wanted is exactly how ember-data behaves per default. All I needed to do, was to have the Epic REST-Service deliver the UserStory ids instead of the inline objects. Ember will get all children in one go - the URL setup is a bit ugly but works - and will use the UserStory rest-service to do the puts.
Ember has a steep but satisfying learning curve.
Thanks for your feedback.
Stefan
I have two different models in my application
App.Foo = DS.Model.extend({
title: DS.attr('string')
});
App.Bar = DS.Model.extend({
title: DS.attr('string')
});
Both foo and bar models get their data from different models in my backend.
They both have some common fields, like title. But others are also different (and I will need different views for them).
However, when displaying a list of them, I would like to be able to merge them and order them as if they were the same object.
Unfortunately from what I see in the ember-data codebase, that doesn't seem to be possible.
Any idea on solutions ?
This has been solved in the latest versions of Ember Data with the implementation of findAll.
Therefore, we can do :
DS.store.findAll(DS.Model)
Which will load all records from all models.