I am working on a new Ember.js project using Rails as backend, and Mongodb as database. Basically it's Starcraft 2 replay analyzer, like ggtracker.com (which powered by angularjs)
Current data structure for my model:
http://paste.kde.org/pd3582db1/
I don't know even how to begin defining it, seems like ember-data is missing a complex type field, and defining each sub model will take like forever (The current model doesn't contain the entire data)
Thanks in advance,
BBLN.
You don't have to use Ember-Data, you can use pojos. Ember-Data is just one of the many "simplified" ways of using models, there is also Ember-Model. That being said, if you created a new Ember object with that huge pojo you'd be accessing it something like this:
var someModel = Ember.Object.create(bigOlJSON);
someModel.get('players.firstObject.abilities.firstObject.blahblahblahblah');
or you could slowly build it up
var players = [];
someModel.get('players').forEach(function(player){
players.push(Ember.Object.create(player));
});
//At this point players is loaded with a slew of player data
That seems a little too monstrous to me.
I'd think it would almost be beneficial to map out all of the models, and set them as embedded models.
See this post for embedded records for Ember Data: Ember-data embedded records current state?
Related
I am new to ember and am trying to migrate an existing application, and would like to know what the recommendation is for modeling a single object that will be reused in multiple components on every page. ie: As part of the initial load, I would like to perform a GET request against a URL like 'https://example.com/currentUser' and get an object back like:
{
name: "Users Name"
email: "user#email.com",
profileImg: "http://example.com/pictureOfUser.png"
... snip ...
}
This object will then be used in components for menus, toolbars, and a form for updating it via a post to the same URL.
What is the best way to model this workflow in ember? Given that this is an incidental object and not the focus of most routes, it does not seem to make sense to specify it as the model of them. Also, does ember data handle cases where a model is a singleton and does not have an ID, or would I need to use something like Ember.$.ajax ?
What do you mean by "where a model is a singleton"?
If you use the ember-data default adapter, then yes, a model needs to have an ID, it's part of the JSONAPI spec. If you already have a backend with different conventions, take a look at extending or swapping out the default adapter.
A service is a singleton, and there is nothing preventing you from making an AJAX call there. You would be losing out on all the nice things that come along with ember-data, but, you can do it.
I have a problem. I have a news model in my background application and my Emberjs app. And there are a lot of news posts can be stored, so I have to divide loading them to some pages or whatever. The problem is - I need to always get a full count of not readed news posts. What is the best way to implement such behaviour in my Ember application?
If you are using Ember Data, I would take a look at Handling Metadata.
The default JSON deserializer looks for a property named meta which could contain something like "totalUnreadPosts": 10
You could get to the metadata with var metaData = store.metaDataFor('newsPost'); which would allow you to get a count without having to load every post.
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
How do you paginate the request for related data? For example, if my Person has a thousand Task models attached to it if I do the following, in RESTful thinking, I would get all of them.
var tasks = person.get('tasks');
That would be way too much data. How do I force some query parameter onto the request that works behind the scenes? Ideally to an endpoint with something like this attached to the end of it.
?&offset=3&limit=3
Here is a fiddle to illustrate what I'm trying to accomplish in the IndexController. I have no idea what the "ember way" is to do paginated requests using ember-data.
It didn't exist when this question was first asked, but there is now an addon called ember-data-has-many-query that seems capable of this, at least for RESTAdapter and JSONAPIAdapter. It appears to have some quirks due to ember-data not yet supporting pagination as a first-class concept. If this makes you uneasy, there is always store.query, but this does require your API to support (in your example) a person_id filter parameter on the /tasks endpoint.
Related:
ember-data issue #3700: Support query params when fetching hasMany relationship
json-api issue #509: Pagination of to-many relationships is underspecified
(it doesn't look like this question involved JSON API, but the discussion is relevant)
As today there is still no default way to handle pagination in ember.
First we should probably look at the more simple thing, pagination of a findAll request.
This can be done with something like .query({page:3}), but leads to some Problems:
This is a good solution for classic pagination, but for a infinite-scroll you still need to manually merge the results.
The results are not cached, so moving forward and backward on an paginated list results in a lot of querys. Sometimes this is necessary if the list is editable, but often its not.
For the second problem I build a little addon called ember-query-cache that hooks into the store and allows you to cache the query results. A very short demo is available here.
Now if we talk about a relationship I would honestly recommend to use top level .query until you have better support from ember-data itself:
store.query('task', { person: get(person, 'id'), page: 3 }
There is nothing bad about it. You get your result and have the relationship in the other direction. It works without any hacking into ember-data as long you don't need caching, and if you need caching it requires the very few hacking I've done in my addon.
We still hope for ember-data to become fully JSONAPI complete, and that would require pagination. I think form an API perspective the best thing would be to have the ability to ask for the next and previous page on the ManyArray returned by the relationship. It would along with the JSONAPI where a next and previous link is provided. But to acomplish that now you would have to hack deep into ember-data without getting a big improvement over the top level .query, which I used successfully in many projects.
From the Ember.js guides on using models, you can also submit a query along with the find() call.
this.store.find('person', { name: "Peter" }).then(function(people) {
console.log("Found " + people.get('length') + " people named Peter.");
});
From the guide:
The hash of search options that you pass to find() is opaque to Ember
Data. By default, these options will be sent to your server as the
body of an HTTP GET request.
Using this feature requires that your server knows how to interpret
query responses.
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'});