Heterogeneous objects in a single URL: are undefined properties set to null? - ember.js

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.

Related

Do I always need a template for each route in Ember 2?

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.

filtering hasMany association in Ember

I'm brand new to Ember and stuck on something that seems very basic. So far in my e-commerce application, I have two models, Product, and Style; a Product has many Styles. For each product, I want to list a subset of the styles (e.g., those that are in stock or out of stock). Coming from a Rails background, I thought I would create a model method in Product called stockedStyles, which filters its styles and returns only those with stocked=true. That didn't work, so I tried another approach of using a controller method, but struck out there too.
Here's the code so far: http://jsbin.com/mufumowabi/1/edit?html,js,console,output
While I would definitely like to know the best practices way of doing this, I'm also curious about other approaches people can suggest that would work but are not recommended -- and why they aren't recommended. Especially when I'm learning something new, I like knowing all the different ways you could do something and then choosing the best/cleanest one.
If there's a tutorial that covers this sort of thing, please send it my way. I couldn't find anything that does this sort of thing, even though it seems so basic.
Lastly, I've found debugging Ember to be somewhat of a black box. For example, for the non-working code posted here, the JS console just says "error". Tips on how I would get more information about why what I'm doing is wrong would be most appreciated as well.
TIA,
fana
I feel your pain. I too came from a rails background expecting similarities in the implementation only to get confused initially. Nobody is ever exaggerating when they claim Ember requires a very large learning investment, but trust me if you stick around it's totally worth it.
Real quick let's take care of a simple goof: You can assign an object property to be either Ember.computed, or function() { /***/ }.property('sdf'); You can't have both. So make that computed function either:
unstockedStyles: Ember.computed.filterBy('styles', 'stocked', false);
or
unstockedStyles: function() {
return this.get('styles').filterBy('stocked', false);
}.property('styles.#each.stocked')
but you can't do both at once.
Ember Models vs Rails Models
Next, the difference with Ember, coming from rails perspective, is that models in Ember.js are intended to be extremely light, serving only as a minimal binding between the data source and the framework overall. Rails takes quite literally the opposite approach, encouraging a very heavy model object (and this is still a large source of contention in the rails community).
In ember.js, the model method helpers are intended to be placed in the controller objects (again, counterintuitive coming from rails). Moving that out, you'll want your models to look like this:
App.Product = DS.Model.extend({
title: DS.attr(),
styles: DS.hasMany('style', { async: true })
});
App.Style = DS.Model.extend({
desc: DS.attr(),
stocked: DS.attr("boolean")
});
The reason for this difference from Rails is that the role of controllers in Ember.js is for "decoration" of your object, whereas in Rails its to handle incoming/outgoing data logic. For each page, you may want to render the same data in different ways. Thus, the model will remain the same, and the controller takes on the burden of encapsulating the extra fluff/computation. You can think of decoration in the same way you were taught the inheritance pattern in OO, with a slight twist:
Let's say you want to have a Person base class (your Ember model), but then you extend it to Teacher and Student subclasses (your controllers) in order to add an additional propertiy that may be from the same type but is not modeled in the same way. For example, Teachers and Students have a many-to-many relationship to courses, but you want to model Students as attending their courses, where Teachers are instructing them. The controller acts as a way to provide such a filter.
ArrayController vs ObjectController
As for the controllers, computed properties for individual records should be placed in the ArrayController's associated ObjectController (we'll call it ProductController), so your controllers now look like this:
App.IndexController = Ember.ArrayController.extend();
App.ProductController = Ember.ObjectController.extend({
unstockedStyles: Ember.computed.filterBy('styles', 'stocked', true)
});
Finally, while Ember.js can automatically associate ObjectControllers with their associated ArrayController for resources defined in your router, you're loading a Product array on the IndexController, so you need to tell IndexController to use ProductController for its item behavior:
App.IndexController = Ember.ArrayController.extend({
itemController: 'product'
});
App.ProductController = Ember.ObjectController.extend({
unstockedStyles: Ember.computed.filterBy('styles', 'stocked', true)
});
Handlebars Binding
Not much here except for a small mistake: while you've enabled a computed property correctly in the proper controller, your {{#each}} loop is instead bound to the original styles array. Change it to use the new property:
{{#each unstockedStyles}}
<li>
{{desc}}, in stock: {{stocked}}
</li>
{{/each}}
And now you're good to go!
JSBin of fixes here: http://jsbin.com/sawujorogi/2/edit?html,js,output

Mixing ember-data and non ember-data models

In my API and on my server I have a model hierarchy like this:
WorkoutPlan->workouts->exercises
I can successfully load that structure with DS.hasMany relationships.
However, my client side view breaks up the plan into weeks so it would be nice to have a client side model structure of
WorkoutPlan->weeks->workouts->exercises
Is it possible to do this if weeks isn't an ember-data model?
If not, should I just transform my JSON so that I can has a hasMany on a Week model (workouts have a week column I could use as a quasi id for the week model).
Or would it be best to keep the current model structure and just somehow filter workouts somehow by week.
Your ember model doesn't have to mimic your server model. There're usually good reasons to de-normalize, minimize the amount of data and simplify the model. For example, the server deals with multiple users, your ember app is likely just concerned with one.
I see two options here. I don't know enough about your model to suggested what's the best.
Add WeekPlan as a model. You could change the serialization logic in your server (if you have an app specific API) or change this during the serialization client side (if this change won't make sense for other API consumers).
Add a filter in your workout router. Also you could have an ArrayController with weeks that simply aggregates the weeks from the workouts in a workout plan.
In general I would lead towards 1, but as I said I don't know enough about your model to make a strong case for either.
Update. Expand on 2
There're two parts to this. The first one is the WeekPlanRoute. That might look something like the following. It's basically responsible to create an array of Weeks and uses that to pass it to pass the workouts to a WorkoutRoute/Controller
App.WeekPlaneRoute = Ember.Route.extend({
model: function(){
// assuming we already have a WorkoutPlan
return workoutPlan.workouts.mapBy('week');
},
);
Then you can navigate to the workouts by using a link-to that passes the week as a parameter:
{{#each}}
{{#link-to 'workouts.index' this}}{{/link-to}}
{{/each}}
In your WorkoutRoute you will filter using that parameter:
Todos.WorkoutRoute = Ember.Route.extend({
model: function(params){
// assuming we already have a WorkoutPlan
return workout.filterBy(params.weekNumber);
}
);
You will also have to change your route to add that dynamic segment for the weekNumber (it has to match that param used above).
this.resource('workouts', {path: '/workouts/:weekNumber'});

Mixing different Models on one Route

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

Is there a way to use an embedded ember-data model association before it's loaded without referencing the root object?

To explain my question, I have concocted a contrived example, so bear with me. I have two related ember-data models that each have the same type of embedded association:
App.Article = DS.Model.extend({
title: DS.attr("string"),
summary: DS.belongsTo(App.Summary, {embedded: true}),
});
App.Book = DS.Model.extend({
title: DS.attr("string"),
summary: DS.belongsTo(App.Summary, {embedded: true}),
});
App.Summary = DS.Model.extend({
text: DS.attr("string"),
});
One great feature of ember-data is that find() will return an object of the correct type which can be used to render the view even before the actual data values have been retrieved from the server. However, I worry this convenience doesn't extend completely to associations.
In cases like this, when multiple types of objects share the same type of associated data, I would like to reuse my view that displays Summary objects for both Book objects and Article objects. In particular, I would like to do the following:
book: Ember.Route.extend({
route: '/book',
connectOutlets: function(router) {
book = App.Book.find(1);
router.get('applicationController').connectOutlet('titleOutlet', 'book', book);
router.get('applicationController').connectOutlet('summaryOutlet', 'summary', book.get('summary'));
},
}),
That is, I would like to have one view for the book-specific stuff, and one view for the summary, which is independent of whether a Book or Article is displayed.
Unfortunately, I can't do book.get('summary') because this returns null if the book has not been populated by store.load(), which is called asynchronously by Book.find().
The alternative seems to be to pass around the book object itself, and always reference nested paths, starting from the root level of the object. In this particular case, the structure of Book and Article are identical, so there is no difference. But if I want to use an association and refer to it independently of its context, it seems there should be another way.
Thoughts?
I've put together a gist illustrating this full example.
Update
I'm resigned to Mike Aski's point that this is not possible. But in order to keep my logic abstract, I create a uniformly named binding pointing to the association, where ever it may be buried, each time I connectOutlet. It's when I call connectOutlet that I know what type of object I'm sending to the controller/view and thus the view can use the binding and doesn't need to know about the rest of the object.
You should try to pass directly the article or book instance as the summaryOutlet context, not the summary. Then, in the view, use:
{{view.content.summary.text}}
instead of what you certainly have for the moment:
{{view.content.text}}
In that way, the binding will be refreshed as soon as record data will be loaded.
I don't see any alternative: While relations are not populated in the instance, you will always get null if you try to traverse them in a synchronous, imperative maner...