I just stumbled on to Ember Data Polymorphic Relationships. I think they would be useful for what I am working on. Here is my main model. This model defines a has many relationship with topics thats both async and polymorphic.
App.Source = DS.Model.extend({
name: DS.attr('string'),
type: DS.attr('string'),
locationType: DS.attr('string'),
locationSpecific: DS.attr('boolean'),
primary: DS.attr('boolean'),
published: DS.attr('boolean'),
sort: DS.attr('number'),
topics: DS.hasMany('topic', {
async: true,
polymorphic: true
})
});
Next we have the Topics with a Base class being 'Topic'
App.Topic = DS.Model.extend({
name: DS.attr('string'),
sort: DS.attr('number'),
source: DS.belongsTo('source')
});
App.RegTopic = App.Topic.extend({
test: DS.attr('number', {
defaultValue: 8
}),
notes: DS.hasMany('notes', {
async: true
})
});
App.SummaryTopic = App.Topic.extend({
number: DS.attr('number', {
defaultValue: 9
})
});
and here is how I call to get the topics
App.TopicsRoute = Ember.Route.extend({
model: function() {
return this.modelFor('source').get('topics');
}
});
When I list the sources, I get a list of the following objects back
{
id: 1
name: "test"
type: "testType"
locationType: "international"
locationSpecific: "true"
primary: true
published: true
sort: 1
links: {
topics: "/topics?sourceId=1"
}
}
then my topic call gets objects like these back
{
id: 4
sourceId: 1
name: Topic 4
type: "regTopic"
sort: 1
}
Am I missing something? Can you not use polymorphic relationships with the 'links' object?
From my understanding of the Polymorphic Relationship, when I make the call to /topics?sourceId=1 its should be essentially loading 2 different topics in one call so then I can display the regTopic and summaryTopic on the same page but in seperate lists and keep them as seperate objects?
Here is an updated jsbin, that seems close to working. http://emberjs.jsbin.com/medojitibo/1/edit?html,js,console,output
The issue I am seeing it that in my controller, the topics are all App.Topic in the list. There is no App.RegTopic or App.SummaryTopic
The links element looks like JSON-API, for which there is an addon. I have had to make modifications for polymorphism in my fork. It may be easier to modify your back-end to support the API specified by Ember Data.
I would not recommend using Ember Data polymorphism unless you really know what you are doing and have convinced yourself it does what you need. Currently it's designed for fairly limited use cases. There are a number of discussions and proposals on the topic that you can track down if you are interested.
You apparently believe, or the API believes, that the format for the links property in the JSON for the source object is an object containing a single (?) topics property which gives the API endpoint for retrieving a polymorphic topic. That's not how it works at all.
The correct syntax for the links property in the returned JSON would be an array of {id, type} tuplets:
{
id: 1
name: "test"
type: "testType"
...
links: [
{ id: 'topic1', type: 'regTopic'},
{ id: 'topic2', type: 'summaryTopic' }
]
}
The format of the data for the association, as shown above, is an array of id/type objects, rather than just an array of ids. This is required for Ember Data polymorphism to work. If you can't arrange for your back-end to produce exactly this kind of format, you will end up patching and hacking a number of methods in adapters and serializers and at the end of the day building your own version of polymorphism, probably not an endeavor you wish to embark on.
Without this special form of JSON for the association properties, Ember Data has no idea of what type of object to go looking for. With it, Ember Data will create the proper instance type of topic for each entry in this topics array, and essentially make a call to find('reg-topic', 'topic1') and find('summary-topic', 'topic2') to get its data. That means that you will also need a separate API endpoint for each subtype of topic. (Matters differ if the topics are embedded, an alternative I won't go into here.)
So far so good, perhaps. But this starts to break down quickly once you start to do more sophisticated things. Let's say you want to fetch/retrieve a topic with a particular ID. One might think that since "topics are polymorphic", you could just do this.store.get('topic', id). But you can't. The store holds RegTopics and SummaryTopics in separate places, and does not connect them with Topic. If you ask for a 'Topic', it does not know to look under different (sub-)models for instances already in the store. So you have to know in advance what the type is and ask the store for the record of that particular type, which sort of defeats the whole purpose of polymorphism. If you simply call the topics endpoint, Ember Data will create a new model instance for Topic--it has no idea that it is supposed to examine the type field of the returned JSON to govern what subtype to turn it into.
The point is that here topics are not polymorphic. In Ember Data polymorphism is not at the model level; it's at the association level. What is polymorphic here, in other words, is not the topic model itself; it's the associations held in the links property on the source model. In other words, Ember Data has no idea about a model itself being polymorphic; it just knows that particular association (hasMany or belongsTo) can hold polymorphic values. For some insights into what might be required to make Ember Data truly support polymorphic models, albeit in a slightly different context, take a look at https://github.com/Bestra/ember-data-sti-guide.
To answer your question in your comment, no, Ember Data polymorphism is not the best way to do what you want. Do not venture out into these uncharted waters. I'd adopt a strategy of "poor man's polymorphism", where your Topic model has a type field, and might or might have some different properties depending on the type. You can then sort and group and filter on type to your heart's content.
Related
I'm been asking a few questions around the same sort of lines and haven't managed to get an answer (perhaps I've been unclear), and just can't figure it out myself.
The quick version of the question is:
Can anybody shed some light on whether its possible to access a property of a related model from a controller?
Its a little tricky, so I'll try and explain the context.
I have the following models:
student
scores: DS.hasMany('score', {async: true}),
name: DS.attr('string')
objective
name: DS.attr('string'),
scores: DS.hasMany('score', {async : true})
score
scoreResult: DS.attr('number'),
objective: DS.belongsTo('objective', {async: true}),
student: DS.belongsTo('student', {async: true})
In my template, I can access attributes of related models with no problem. For example, something like this:
{{#each student in model}}
{{student.name}}
{{#each score in student.scores}}
{{score.objective.name}}
{{score.Result}}
{{/each}}
{{/each}}
What I ultimately want to do is create a "score" property on the student controller that loads the appropriate score/result when I choose an objective elsewhere. But I'm falling at the first hurdle.
Though I can access attributes of related models in the templates, I can't seem to in the controller. I'm expecting that I can do something like this in "student controller":
score: function(){
var selectedObjID = 5;
return this.get('model.scores').findBy('objective_id', 1).get('scoreResult');
I've tried every variation I can think of, including what I would have expected from reading the guides/api.
I feel like I'm missing something obvious - surely this should have been done enough before for there to be some documentation?
Its also important to do in the one route - I don't want details dealt with via another route.
--------------------------------Edit----------------------------
Thanks, I've tried adjusting your code a little, but I can't get it to work. Any chance you'd be able to help me correct it based on the model's above?
score: function() {
var scores = this.get('model.scores');
if (!scores) {
return "No scores";
}
var score = scores.findBy('objective', 1);
if (score === undefined) {
return "Not resolved/No score";
}
return score.get('scoreResult');
}.property('model.scores.#each.objective')
Though I can access attributes of related models in the templates, I can't seem to in the controller.
Based on this, I can take a good guess and say that your problem is unresolved promises. Ember Data doesn't always have synchronous access to related models, so it always returns a promise that will resolve later. More likely than not, you're trying to use that promise as if it's already resolved even though it's not yet. When writing computed properties based on Ember Data relationships, you should always write your properties as if you will be given empty data the first time through. Usually this means checking for null references and making sure the property updates when the promise resolves. To get to the point, here's your property that should work:
score: function() {
var scores = this.get('model.scores');
if (!scores) {
return;
}
var score = scores.findBy('objective_id', 1);
if (score === undefined) {
return;
}
return score.get('scoreResult');
}.property('model.scores.#each.objective_id')
The first time this property calculates this.get('model.scores') will be an empty PromiseArray, so you won't get any data from it. For those scenarios, just return and leave your property undefined (for now). But since the property is dependent on model.scores.#each.objective_id, the property will recalculate as soon as the PromiseArray resolves and the data is available. So this property won't have the right value until it runs 2 or 3 times, but it will eventually have the right value. The reason you don't get these issues when using the properties in a template is because Ember takes care of that for you.
Sorry for the long explanation, but there's a lot to learn in the area of unresolved promises and asynchronous computed properties (much more than I've posted). It's unfortunately one of those things that you just pick up as you learn Ember more. If I haven't made things clear or the above property doesn't work, let me know in the comments and I'll update and try to clarify.
I'm trying to update an app from Ember Data 1.0.0-beta.9 to 1.0.0-beta.11, and quite a bit seems to have changed. Specifically, I'm running into issues with finding out if a model instance does indeed have an associated model instance.
A = DS.Model.extend({
b: belongsTo('b', { async: true }),
});
B = DS.Model.extend({
a: belongsTo('a', { async: true }),
});
In Ember Data 1.0.0-beta.9, a.get('b') would simply return null if no associated model is found. That makes it easy to filter by computed property macros.
In Ember Data 1.0.0-beta.11, a.get('b') returns a promise, which makes it much harder to use in computed property macros. If the promise is fulfilled and the content of the promise is null, there's no associated record. But I have no idea whether it is possible to implement this check inside a Ember.computed.filter.
I have quite a few Ember.computed.filters probing quite a few Ember.isEmpty(a.get('b'))s, so I'm looking for a good way to check whether an object's async relationship is empty. Am I missing something obvious, like a built-in Ember Data api call? How would you implement such a check, if you need to filter by associated property presence/absence?
Well, to answer my own question, I got around this by filtering for Ember.isEmpty(a.get('b.id')), b/c I usually deal with persisted records and was able to ship around the edge cases. Sometimes it's so simpleā¦ :D
if the related record is just created and isNew is true, checking the id property would also return null. In my case this causes unexpected results. How I managed to overcome this is by checking the content property instead. For a newly created record this will return the model class, and null if the related object is empty.
Quite new to Ember, I'm building an app and encounter a difficult problem.
MY SITUATION
I have a single page, which should display a list of elements, named "containers", and inside each container, I want to display several "contents".
For now, here is a very simplified version of my JS code (just to give you an idea):
App.ContainersRoute = Ember.Route.extend({
model: function() {
return this.store.findAll('container');
}
});
App.Container = DS.Model.extend({
title: DS.attr('string'),
contents: DS.hasMany('content')
});
App.Content = DS.Model.extend({
[...]
});
I will pass on the template part, wich is not a problem here, it's quite classic.
So, this code is working, I have my list of containers, and a list of the propers contents for each of them.
MY PROBLEM = "A WHITE SCREEN OF DEATH"
Indeed, as I fetch my data from a server in JSON, for now, before Ember transitionning to my page, I have a wait the loading of all the data.
The problem is that, "containers" data is quite small and static, so my server can answer it in less than a second, but, for the "contents" part, it is very long (between 20 to 40 seconds), as my server needs to do a lot of work to recover these date from my DB.
It would be ok for me to wait this long if my page was already loaded (navigation + containers without the contents), but right now, I just have a white page during 30s with no idea except the console to know is everything is ok.
Of course, I could use a "loading bar" thanks to "beforeModel" in the Route, but I would very much prefer to have access at least to the UI.
FOR YOU, WHAT COULD BE A GOOD SOLUTION FOR THIS PROBLEM?
Thanks a lot in advance for your help :)
I'd make content's async and fetch that after the fact so you can give a more responsive look and feel.
App.Container = DS.Model.extend({
title: DS.attr('string'),
contents: DS.hasMany('content', {async: true})
});
App.Content = DS.Model.extend({
[...]
});
That means Ember Data will make a callback for that data when requested, and not expect it at the same time as the container data.
Example: http://emberjs.jsbin.com/OxIDiVU/1045/edit
Additionally you can add a loading route in the resource above your Containers resource which could give feedback about how your fetching data. http://emberjs.com/guides/routing/loading-and-error-substates/
Example: http://emberjs.jsbin.com/cerid/2/edit
Personally I like the async idea more though, show the fact that you have containers, but then show contents as loading/empty.
What is the "appropriate" way in Ember to send a parameter from one route to another? For instance, I have two routes defined as such:
this.resource('activities', { path: '/activities/:on_date' }, function() {
this.route('new');
});
when on the ActivitiesRoute the user is presented with a dropdown of possible activities. When they choose something it transitions to the ActivitiesNewRoute:
this.transitionToRoute('activities.new');
and I know there is a second parameter available in the transitionToRoute(route,model) method but it's meant for passing in a model and I'm assuming this shouldn't be repurposed for other parameter passing. In this case the dropdown choice is picking an Action model id and the model for ActivitiesNew is a Activity.
Here are my three guesses at ways that might work:
1) Make it a router parameter
I supposed I could change ActivitiesNew to include a "parameter" as part of the route:
this.route('new', { path: '/new/:my_parameter' });
I'm not sure I'd really like to have it becoming part of the URL path but if this was the prevailing convention then I'd live with that.
2) Get a handle, post transition
Immediately following the transitionToRoute call I could set a property of the new controller class. Not sure if the controller would be setup yet but I'm imagining something like:
this.transitionToRoute('activities.new');
this.get('target').controllerFor('activities.new').set('my_parameter', myValue);
3) Use model parameter
this.transitionToRoute('activities.new',myValue);
I suspect that this is a major no-no. I haven't looked into the Ember code to know if this could work but it seems against convention so this is my "bad option".
transitionTo & transitionToRoute return a "promise-like" object. The parameter this object is resolved with is the route, from which you can access controller and currentModel. So a nice clean way to pass information to a route to which you are transitioning is:
var my_param = ....;
this.transitionToRoute('activities.new').then(function(newRoute) {
newRoute.currentModel.set('someProperty', my_param);
//or
newRoute.controller.set('someProperty', my_param);
});
EDIT/RANT:
note that in most cases, you do want to use needs, and bind things between controllers. However, there are certainly instances when you have things that depend on the logic of a route transition -- eg., controllerB has state X if we came to routeA from routeB, but state Y if we came from routeC. In that case, my answer is valuable.
The primary value of stack overflow to the development community is not the immediate answers you get to questions you post, but the massive ever growing wealth of googleable development knowledge. When you "infer" from a user's question that they "should" be doing something other than what they are asking how to do, you may be right (or you may be just incapable of imagining their particular circumstance), but if you answer only with your recommendation/rule/aphorism/cargo-cult-dictum instead of answering the ACTUAL QUESTION, you diminish the value of everybody else's google searches. If you want to tell someone to do something other than what they're asking, do it in a comment, or in a footnote to an answer to the actual question.
You can use the needs API (Read about it here):
App.ActivitiesNewController = Ember.ObjectController.extend({
needs: ['activities']
// Bind the property you need
actionTemplateBinding: 'controllers.activities.actionTemplate'
});
So what you actually need is to pass a parameter between controllers, which is exactly what needs is for. Plus, binding the property with needs you ensure it is in sync at all times, instead of relying on setupController being called.
You could use query-params (http://guides.emberjs.com/v1.10.0/routing/query-params/), as follows:
this.transitionToRoute('activities.new', {queryParams: {my_param: 'my_value'});
In order to be able to receive my_param in the new controller, you would also need to define the following lines:
App.ActivitiesNewController = Ember.ObjectController.extend({
queryParams: ['my_param'],
my_param: ''
...
});
A drawback of this solution is that the value of my_param will be serialized in URL - so it would not be suitable for some sensitive information you may want to pass between routes.
I'll answer my question with what I've decided to go with for now but keep it open for a a few days to see if anyone comes back with a more experienced answer. My answer may very well be perfectly fine ... it works anyway.
I've gone with a variation of #2 from the question. The difference is that that rather than trying to set a property in the ActivitiesNew controller from Activities controller I do the the opposite:
In ActivitiesNewRoute:
App.ActivitiesNewRoute = Ember.Route.extend({
model: function(params) {
return this.store.createRecord('activity');
},
setupController: function(controller,model) {
controller.set('actionTemplate', this.controllerFor('activities').get('actionTemplate'));
}
});
Still interested in hearing from people if there's a better way of doing this.
Transition to route with params and set model
yourAction:->
model = 'your-model'
route = 'your.path.to.toute'
routeLoad = #transitionToRoute route,
id: model.get 'id'
routeLoad.then (route) ->
route.set 'controller.model', model
return
Is there any way to get at what an Ember.js object really contains in the JavaScript console. If you do console.log(this), you will get almost the same data structure for almost any object, something like this:
That's not super helpful, and it gives you no idea what attributes are actually set on the object.
So far, the only way I've found to debug is to try and console.log(this.get('whatever')) for any conceivable name, but it's hard to guess what's available.
Is there any way to dig deeper into the object's data?
Ember provides several methods to help debug an object from console:
Object.toString prints identity of any ember object
App.Person = Em.Object.extend()
person = App.Person.create()
person.toString()
//=> "<App.Person:ember1024>"
Ember.inspect converts the object into a useful string description
var object = Ember.Object.create({
firstName: 'Hansi',
lastName: 'Hinterseer',
age: 58
});
console.log( Ember.inspect(object) );
// {__ember1331067974108_meta: [object Object] , firstName: Hansi , lastName: Hinterseer , age: 58}
Ember.keys returns all of the keys defined on an object or hash
console.log(Ember.keys(this));
There is also the App.__container__ object which, if you know what name your objects are registered to the ember app with, will allow you to grab any object you need while debugging from any environment.
A couple of examples are
App.__container__.lookup('store:main') # Gets the store
App.__container__.lookup('controller:blog') # Gets the blog controller
ember-chrome-devtools is a nice way to solve this problem now...
If you're trying to inspect an Ember Data record, you can call the serialize method on it from your console, this will give you the object as your external data source expects.