I have Order and PaymentMethod models in Ember with a many-to-one relationship like so:
order.js
export default DS.Model.extend({
paymentStatus: DS.attr('number', { defaultValue: 1 }),
paymentMethod: DS.belongsTo('payment-method'),
})
payment-method.js
export default DS.Model.extend({
name: DS.attr('string'),
orderPaymentMethods: DS.hasMany('order')
})
The order is saved to the database - all fine. I then have a route review-order, which obtains the order model (my API is only returning order models for the current logged in user)
review-order.js
export default Route.extend({
model() {
const order = this.store.findAll('order', { include: 'payment_method' }).then((orders) => {
return orders.filterBy('paymentStatus', 1).get('lastObject');
});
return order;
}
});
In my review-order template, I am printing out the payment method relationship like so
{{model.paymentMethod.name}}
This works fine I transition to the review-order route after the order is created - it prints the name of the payment method. However, if I refresh the review-order page, this is lost, and I have no idea why!
thanks for the full and detailed question! I am going to attempt an answer but I don't have 100% of the information that I would need to be sure this will answer the issue for your exact situation but hopefully it will still be useful and you can extract what you need from it.
The overall problem that you mentioned, that the data is not fully loaded correctly on a refresh, is likely caused by the API response not having all of the information that it needs. If you check the Web Dev Tools on chrome you will be able to verify that this is the case.
If you open dev tools, open the network tab and find the query that is loading your data then we can verify this. I don't know how familiar you are with dev tools but if you open them and refresh the page you will see all of the requests that the browser made. You can then filter by XHR to find the API query:
You will then be able to click on the request in question and use the preview tab to explore the data that is available:
By looking at this data you will be able to verify if the required relationship is set up, and you will also be able to very if the correct data is also included in the includes section of the JSON:API response. For an example of what this should look like you can check out the examples page for the official JSON:API documentation
We spent a little bit of time trying to replicate your issue using Mirage so that we could show the exact problem that you were having. It is a bit of a toy example but it does replicate the error and shows how the method I have described above fixes the issue.
Firstly I replicated your models exactly as you described them, then I created a Mirage endpoint to handle requests to get a single order:
this.get('/orders/:id', (schema, request) => {
let responseData = {
data: {
id: request.params.id,
type: 'orders',
attributes: { 'payment-status': 1 },
relationships: {
"payment-method": {
data: { id: "42", type: "payment-methods" }
}
}
}
};
if(request.queryParams.include === "payment_method") {
responseData.included = [{
type: "payment-methods",
id: "42",
attributes: { name: "Credit Card" }
}];
}
return responseData;
});
You will see that if the queryParams includes ?include=payment_method it makes sure that the payment method is included in the response JSON.
One other thing that I noticed about your example was that your model() hook in the review-order.js route seems a bit unconventional 🤔When I was going through this example I made use of a dynamic segment on the review-order route and made sure that I passed the order object when transitioning to it. Here is my Router definition:
Router.map(function() {
this.route('review-order', { path: '/review/:id'});
});
I then created a simple button with a createOrder() action to simulate creating an order:
actions: {
createOrder() {
let paymentMethod = this.store.createRecord('payment-method', {
name: 'Credit Card'
});
let order = this.store.createRecord('order', {
id: 'not-null',
paymentStatus: 1,
paymentMethod: paymentMethod,
});
// we don't actually save because I don't have a backend setup correctly
// order.save().then(() => { })
// when we're done transition to review route with the model
this.transitionToRoute('review-order', order);
}
}
As you can see, I am including the order object in the transitionToRoute() call.
Then we can simplify the model() hook in your route a little bit:
model(params) {
return this.store.findRecord('order', params.id, { include: 'payment_method'})
}
I hope that helps you figure out your issue 😄
This question was answered as part of "May I Ask a Question" Season 2 Episode 4. If you would like to see us discuss this answer in full you can check out the video here: https://www.youtube.com/watch?v=W1udsGt8F9g
Related
This is a similar question to this one, except this is for the latest versions of Ember and Active Model Serializers (0.10.2).
I have a simple Parent:Child relationship.
app/models/trail.js
import Ember from 'ember';
import DS from 'ember-data';
export default DS.Model.extend({
name: DS.attr(),
// relationships
employees: DS.hasMany('employee', { async: true }),
});
app/models/employee.js
import DS from 'ember-data';
import Person from '../models/person';
export default Person.extend({
status: DS.attr(),
statusCode: DS.attr(),
});
app/models/person.js
import Ember from 'ember';
import DS from 'ember-data';
export default DS.Model.extend({
avatarUrl: DS.attr(),
firstName: DS.attr(),
lastName: DS.attr(),
fullName: Ember.computed('firstName', 'lastName', function() {
return `${this.get('lastName')}, ${this.get('firstName')}`;
}),
});
When I create a new Trail, and select two employees for the 'hasMany', the following json arrives the server (from the Rails log):
{"data":
{"attributes":
{"name":"TEST3",
"gpx-file-url":"a url",
"distance-value":"5"},
"relationships":
{"employees":{"data":[]}}, "type":"trails"}}
My question is, what has happened to the employees? Where are the id's of the employees (they already exist both in the database and in the Ember Store - ie, I am not trying to create child records in this request).
EDIT
I just found this question, which explains that the id's for a hasMany relationship are not sent by Ember's JSONAPISerializer to the API - since the foreign key here actually has to be persisted in each child record. So essentially by 'selecting' employees, you need to save the fact that they now have a parent. So the selected employee records need to be persisted.
But my understanding was that this all works "out of the box" and that Ember would automatically fire a POST request to do this, but that seems to not be the case.
This then gets to the real question - how do I update those children?
UPDATE - BOUNTY ADDED AS THIS HAS QUESTION HAS EVOLVED
After further analysis, it became clear that a new model was required - Assignments. So now the problem is more complex.
Model structure is now this:
Trail
hasMany assignments
Employee
hasMany assignments
Assignment
belongsTo Trail
belongsTo Employee
In my 'new Trail' route, I use the fantastic ember-power-select to let the user select employees. On clicking 'save' I plan to iterate through the selected employees and then create the assignment records (and obviously save them, either before or after saving the Trail itself, not sure which is best yet).
The problem is still, however, that I don't know how to do that - how to get at the 'selected' employees and then iterate through them to create the assignments.
So, here is the relevant EPS usage in my template:
in /app/templates/trails/new.hbs
{{#power-select-multiple options=model.currentEmployees
searchPlaceholder="Type a name to search"
searchField="fullName"
selected=staff placeholder="Select team member(s)"
onchange=(route-action 'staffSelected') as |employee|
}}
<block here template to display various employee data, not just 'fullName'/>
{{/power-select-multiple}}
(route-action is a helper from Dockyard that just automatically sends the action to my route, works great)
Here is my model:
model: function () {
let myFilter = {};
myFilter.data = { filter: {status: [2,3] } }; // current employees
return Ember.RSVP.hash({
trail: this.store.createRecord('trail'),
currentEmployees: this.store.query('employee', myFilter).then(function(data) {return data}),
});
},
actions: {
staffSelected (employee) {
this.controller.get('staff').pushObject(employee);
console.log(this.controller.get('staff').length);
},
}
I only discovered today that we still need controllers, so this could be my problem! Here it is:
import Ember from 'ember';
export default Ember.Controller.extend({
staff: [] <- I guess this needs to be something more complicated
});
This works and I see one object is added to the array in the console. But then the EPS refuses to work because I get this error in the console:
trekclient.js:91 Uncaught TypeError: Cannot read property 'toString' of undefined(anonymous function) # trekclient.js:91ComputedPropertyPrototype.get # vendor.js:29285get #
etc....
Which is immediately follow by this:
vendor.js:16695 DEPRECATION: You modified (-join-classes (-normalize-class "concatenatedTriggerClasses" concatenatedTriggerClasses) "ember-view" "ember-basic-dropdown-trigger" (-normalize-class "inPlaceClass" inPlaceClass activeClass=undefined inactiveClass=undefined) (-normalize-class "hPositionClass" hPositionClass activeClass=undefined inactiveClass=undefined) (-normalize-class "vPositionClass" vPositionClass activeClass=undefined inactiveClass=undefined)) twice in a single render. This was unreliable in Ember 1.x and will be removed in Ember 3.0 [deprecation id: ember-views.render-double-modify]
So I imagine this is because the examples in the documentation just uses an array containing strings, not actual Ember.Objects. But I have no clue how to solve this.
So, I decided to throw away the controller (ha ha) and get creative.
What if I added a property to the Trail model? This property can basically be a 'dummy' property that collected the selected employees.
in /app/models/trail.js
selectedEmps: DS.hasMany('employee', async {false})
I set async to false since we will not persist them and before saving the new Trail I can just set this to null again.
in /app/templates/trails/new.js
{{#power-select-multiple options=model.currentEmployees
searchPlaceholder="Type a name to search"
searchField="fullName"
selected=model.selectedEmps placeholder="Select team member(s)"
onchange=(action (mut model.selectedEmps)) as |employee|
}}
<block here again/>
{{/power-select-multiple}}
This works, it doesn't 'blow up' after selecting the first employee. I can select multiple and delete them from the template. The control seems to work fine, as it is mutating 'model.selectedEmps' directly.
Now, I think this is a hack because I have two problems with it:
If I change the 'mut' to an action, so I can add further logic, I
cannot figure out how to access what is actually stored in the
propery 'model.selectedEmps'
Even if I can figure out (1) I will have to always make sure that
'selectedEmps' is emptied when leaving this route, otherwise the
next time this route is entered, it will remember what was
selected before (since they are now in the Ember.Store)
The fundamental issue is that I can live with 'mut' but still have the problem that when the user hits 'Save' I have to figure out which employees were selected, so I can create the assignments for them.
But I cannot figure out how to access what is selected. Maybe something this Spaghetti-Monster-awful mess:
save: function (newObj) {
console.log(newObj.get('selectedEmps'));
if (newObj.get('isValid')) {
let emp = this.get('store').createRecord('assignment', {
trail: newObj,
person: newObj.get('selectedEmps')[0]
})
newObj.save().then( function (newTrail) {
emp.save();
//newTrail.get('selectedEmps')
// this.transitionTo('trails');
console.log('DONE');
});
}
else {
alert("Not valid - please provide a name and a GPX file.");
}
},
So there are two problems to solve:
How to get the selected employees, iterate and create the
assignments.
How to then save the results to the API (JSON-API using Rails). I
presume that newObj.save and each assignment.save will take care
of that.
UPDATE
The developer of EPS kindly pointed out that the action handler receives an array, since I changed to using a multiple select, not a single select as it had been earlier. So the action is receiving the full array of what is currently selected. DOH!
I was thus able to update the action handler as follows, which now successfully stores the currently selected employees in the staff property of the controller. One step closer.
staffSelected(newList) {
existing.forEach(function(me){
if (!newList.includes(me)) {
existing.removeObject(me); // if I exist but the newList doesn't have me, remove me
}
});
newList.forEach(function(me){
if (!existing.includes(me)) {
existing.pushObject(me); // if I don't exist but the newList has me, add me
}
});
}
Perhaps not the best way to intersect 2 arrays but that's the least of my concerns at 4am on a Saturday night. :(
FINAL PROBLEM UPDATE - how to save the data?
Ok, so now that I can get the selected employees, I can create assignments, but still cannot figure out what Ember requires for me to save them, this save action throws an error:
save: function (newObject) {
if (newObject.get('isValid')) {
let theChosenOnes = this.controller.get('theChosenOnes');
let _store = this.get('store');
theChosenOnes.forEach(function (aChosenOne) {
_store.createRecord('assignment', {
trail: newObject,
person: aChosenOne,
});
});
newObject.save().then(function (newTrail) {
newTrail.get('assignments').save().then(function() {
console.log('DONE');
});
});
}
get(...).save is not a function
The problem with your final update is that in Ember Data 2.x, relationships are asynchronous by default, so what's returned from newTrail.get('assignments') is not a DS.ManyArray, which has a .save, but a PromiseArray, which doesn't have that.
You need a small tweak to do this instead, so you call .save on the resolved relationship:
newObject.save().then(function (newTrail) {
newTrail.get('assignments').then(assignments => assignments.save()).then(function() {
console.log('DONE');
});
});
I'm trying to wrap my head around dynamic segments and I want to be able to use a slug or other property instead of the id. When I can get things working it feels like a fluke. (I'm using ember 2.7+)
I've been looking at the following sources/threads for ideas:
https://guides.emberjs.com/v1.10.0/cookbook/ember_data/linking_to_slugs (1.10)
http://discuss.emberjs.com/t/slugs-for-replacing-id-path-for-dynamic-segments
I plan on using ember-data, but I want to ensure I'm in control - and so I don't want to use the :post_slug / underscore style that has some built in magic that I want to avoid.
Here is an ember-twiddle
Here are step-by-step commits in a github repo
My thought process
1. Conceptually, lets say I need a list of cats - so I need to describe the model for what a 'cat' is.
models/cat.js
import Model from "ember-data/model";
import attr from "ember-data/attr";
export default Model.extend({
name: attr('string'),
slug: attr('string')
});
2. define where the dynamic segment will be in the url. I'm going to use catId to prove a point instead of cat_id or :id like most of the tutorials I've seen. For this example, I'm also writing an actual app structure instead of the smallest router possible - to test the edges of this. + what if I needed something like this later: animals.com/cats/:catId/best-friend/:dogId
router.js
Router.map(function() {
this.route('index', { path: '/' });
this.route('cats', { path: '/cats' }, function() {
this.route('index', { path: '/' }); // list of cats
this.route('cat', { path: '/:catId' }); // cat spotlight
});
});
3. pull in the catData into the store ~ in the /cats route
routes/cats.js
import Ember from 'ember';
const catData = [
{
id: 1,
name: 'Dolly',
slug: 'dolly'
},
{
id: 2,
name: 'kitty cat',
slug: 'kitty-cat'
},
{
id: 3,
name: 'Cleopatra',
slug: 'cleo'
}
];
export default Ember.Route.extend({
model() {
return catData;
// return this.get('store').findAll('cat'); // with mirage or live api
}
});
Update from comments:
I do not believe that you can use queryRecord with your test data.
Ember data plays dumb with query and queryRecord; it doesn't assume
anything about your data and just passes the call on to your server.
~ #xcskier56
So this kinda blows my twiddle as is. The git repo example is Mirage.
4. create the templates... + set up the 'cat' route. The records are in the store... right? so I should be able to 'peek' at them based on id. The docs use params - but -
Ember will extract the value of the dynamic segment from the URL for
you - and pass them as a hash to the model hook as the first argument
: ~ and so the params object name isn't special and could really be anything you wanted... and is just replaced with a hash - so to that point / I'm using 'passedInThing' just to assert control over the confusing conventions (many tutorials use param instead of params)
routes/cats/cat.js
model( passedInThing ) {
return this.store.peekRecord('cat', passedInThing.catId );
} // not going to happen - but in theory...
model( passedInThing ) {
return this.store.queryRecord('cat', { slug: passedInThing.catId } );
}
5. At this point, I should be able to navigate to the url /cats/2 - and the 2 should get passed through the model hook - to the query. "Go get a 'cat' with an id of 2" --- right??? ((the twiddle example uses a hard-coded set of catData - but in my other attempts I'm using mirage with a combination of fixtures and dynamic slugs: https://github.com/sheriffderek/dynamic-segments-tests/commits/queryRecord
6. Typing in the segment works - but for link-to helpers I need to pass in the explicit cat.id
{{#link-to 'cats.cat' cat.id}}
<span>{{cat.name}}</span>
{{/link-to}}
7. I can get all that working - but I don't want an ID in the URL. I want cats/cleo with the 'slug' ~ in theory I can just switch catId for catSlug and cat.id to cat.slug etc - but that is not the case. I've seen many tutorials outlining this but they are outdated. I've tried passing in { slug: params.slug } and every combo of find query and peek etc. Sometimes the url will work but the model wont render - or the opposite.
8. This seems like 101 stuff. Can anyone explain this to me? Oh wise emberinos - come to my aid!
UPDATES
A nice video showing how to use serialize() in this case
There is supposedly an example coming to the Ember tutorial, but I haven't seen it land yet.
I've struggled with this same issue, and AFIK there is not a way to handle this without querying the server. While this is old and the code is a little out dated the logic still stands. http://discuss.emberjs.com/t/use-slug-for-page-routes-id-for-store-find/6443/12
What I have done is to use store.queryRecord() and then my server is able to return a record fetched via a slug.
This is what the route would look like:
model: function (params) {
return this.store.queryRecord('cat', {slug: params.catSlug})
}
This will enable you to not expose the ID in the url, but it will issue a query to the store every single time that the model gets hit. There seems to be some discussion of caching query and queryRecord in ember-data, but nothing working yet.
https://github.com/emberjs/data/issues/1966
Other helpful resources:
How to cache query result in ember data
Cache a record when using Query Params in the call? Ember-data
I'm a newbie to Ember Data and all I've done to date is FIXTURE data but today I'm trying to graduate to the real deal and am realising that I don't know enough about how to connect the model and the API's call signature.
Specifically I'd like to be able to call an endpoint GET /activities/[:user_id]/[date]. This would load an array of "Activity" objects but only those for a given date. I know that I can offset the API's directory with:
DS.RESTAdapter.reopen({
namespace: 'api'
});
In my case the api prefix is appropriate. I think I should also be able to get the date component solved by setting up a route something like this:
this.resource('activities', { path: '/activities' }, function() {
this.route('by_date', {path: '/:target_date'});
});
The above is just an educated guess because I'm completely at a loss on how to get the user_id component into the URL signature. Can anyone help? Are there any good tutorials or examples of basic Ember Data use cases?
Also, because I know I'm going to run into this next ... how does one add parameters to the url string (aka, GET /foobar?something=value) versus parameters to the URL itself (like above)?
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
UPDATE
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
I've implemented the suggestions from #intuitivepixel but am still having some problems ...
First off I tried to hard code the values for userId and dateBy:
Router:
this.resource('activities', { path: '/activities' }, function() {
this.route('by_date', {path: '/:user_id/:by_date'});
});
Route:
App.ActivitiesByDateRoute = Ember.Route.extend({
serialize: function(activity) {
return {
userId: 1,
dateBy: "2013-07-01"
};
}
});
Sadly that did not work. I think I understand why -- although I don't have a quick way to fix this -- but more disturbing for me was that when I manually put in the parameters into the URL: http://restful.service.com/api/activities/1/2013-07-01. The results are quite surprising to me:
Initially the debugging messages suggest a success:
This however, is not correct as no network requests are actually made
If you reload the browser, it will now go out and get the Activities but to my surprise it also goes out to find the specified user. Hmmm. That's ok, the user 1 is pulled back successfully.
The Activity, however, is just a GET /activities call which fails because this endpoint needs the user and date qualifier to work. Why weren't these included in the request?
I know that I can offset the API's directory with:
You can also set a different URL of our API if it's the case:
DS.RESTAdapter.reopen({
url: 'http://myapihost.com',
namespace: 'api'
});
This would produce a URL like http://myapihost.com/api/
The above is just an educated guess because I'm completely at a loss on how to get the user_id component into the URL signature.
Following your example and adding the user_id dynamic segment, let's say you have this router map definition:
this.resource('activities', { path: '/activities' }, function() {
this.route('byDate', {path: '/:user_id/:target_date'});
});
and this is your {{linkTo}} helper:
{{#each activity in model}}
{{#linkTo 'activities.byDate' activity}}
{{/each}}
then to build your url out of multiple dynamic segments you could hook into your route's serialize function and returning a hash composed out of the dynamic segments your URL needs:
App.ActivitiesByDateRoute = Ember.Route.extend({
serialize: function(activity) {
return {
user_id: activity.get('userId'),
target_date: activity.get('targetDate')
}
}
});
The code above will generate a URL like /activities/[userId]/[targetDate] when the {{linkTo}} link is clicked. The example assumes that the properties you need to build your URL are available in your Activity model.
Also, because I know I'm going to run into this next ... how does one add parameters to the url string (aka, GET /foobar?something=value) versus parameters to the URL itself (like above)?
This kind of URL queries are not yet supported by the ember framework, but the are good workarounds/projects that try to deal with that missing feature like: https://github.com/alexspeller/ember-query, and if this PR get's merged some day then you will have them also soon in ember, but for the time beeing you could use the above mentioned library to have support for custom queries. For the current status whether the lib get's merged or not have look here: http://discuss.emberjs.com/t/query-string-support-in-ember-router there is a discussion going on.
Hope it helps.
UPDATE:
THIS IS A NON-ISSUE
(see below)
So I wrote a jsfiddle to show the bad behavior except the fiddle works! and my real code doesn't. The only difference is I am using the RESTAdapter in my real code so the data is pulled from server instead of FIXTURES.
In the jsfiddle: first click 'Simulate 1st manual load', then click the 2nd button to see it work properly (i.e. loading new or updated data to the store multiple times in a row)
http://jsfiddle.net/iceking1624/NZZ42/4/
The Issue
I am sending updated information over websockets to my Ember App. I successfully set up a listener to trigger a function on the correct controller and am able to update records the first time. But all successive attempts do not update the store and I wonder if this has to do with the state of the store? But I am unsure of how to handle if that is the case.
This is the code that updates or adds the records that come over websockets:
App.SessionController = Ember.ObjectController.extend({
updateReceived: function(data) {
console.log(data);
DS.defaultStore.load(App.Choice, data.choice);
DS.defaultStore.load(App.Question, data.question);
}
});
Notice the console.log(data) part. Every single time I send updated data via websockets, updateReceived is called and the correct data is logged every time, but DS.defaultStore.load(...) only works the first time.
The reason I update both App.Question & App.Choice is because they have a relationship:
App.Question = DS.Model.extend({
"choices" : DS.hasMany('App.Choice')
});
App.Choice = DS.Model.extend({
"question" : DS.belongsTo('App.Question')
});
I don't think the code below is relevant to the issue but just in case someone is interested, this is how I listen for events over websockets (using socket.io):
App.SessionRoute = Ember.Route.extend({
enter: function() {
this.socket = io.connect('http://10.0.1.4')
},
setupController: function(controller, model) {
var self = this;
this.socket.on('update', function(data) {
self.controller.send('updateReceived', data)
})
}
});
Are there any suggestions for how I can continuously load new or updated records directly into the store (and not just once)?
UPDATE:
The code is indeed correct. The new data was loading into the store just fine but I wasn't re-rending a view correctly when new/updated information was loaded into DS.defaultStore
I don't want to delete this question since others may find the information in it useful but vote how you like. I'm sorry I didn't catch this before writing the question.
I'm using an ArrayController in my application that is fed from a Ember Data REST call via the application's Router:
postsController.connectOutlet('comment', App.Comment.find({post_id: post_id}));
For the Post UI, I have the ability to add/remove Comments. When I do this, I'd like to be able to update the contentArray of the postsController by deleting or adding the same element to give the user visual feedback, but Ember Data is no fun:
Uncaught Error: The result of a server query (on App.Comment) is immutable.
Per sly7_7's comment below, I just noticed that the result is indeed DS.RecordArray when there is no query (App.Comment.find()), but in the case where there is a query (App.Comment.find({post_id: post_id}), a DS.AdapterPopulatedRecordArray is returned.
Do I have to .observes('contentArray') and create a mutable copy? Or is there a better way of doing this?
Here is what I ended up implementing to solve this. As proposed in the question, the only solution I know about is to create a mutable copy of the content that I maintain through add and deletes:
contentChanged: function() {
var mutableComments = [];
this.get('content').forEach(function(comment) {
mutableComments.pushObject(comment);
});
this.set('currentComments', mutableComments);
}.observes('content', 'content.isLoaded'),
addComment: function(comment) {
var i;
var currentComments = this.get('currentComments');
for (i = 0; i < this.get('currentComments.length'); i++) {
if (currentComments[i].get('date') < comment.get('date')) {
this.get('currentComments').insertAt(i, comment);
return;
}
}
// fell through --> add it to the end.
this.get('currentComments').pushObject(comment);
},
removeComment: function(comment) {
this.get('currentComments').forEach(function(item, i, currentComments) {
if (item.get('id') == comment.get('id')) {
currentComments.removeAt(i, 1);
}
});
}
Then in the template, bind to the this computed property:
{{#each comment in currentComments}}
...
{{/each}}
I'm not satisfied with this solution - if there is a better way to do it, I'd love to hear about it.
A comment will be too long...
I don't know how do you try to add a record, but you can try to do this: App.Comment.createRecord({}). If all goes right, it will update automatically your controller content. (I think the result of App.Comment.find() works as a 'live' array, and when creating a record, it's automatically updated)
Here is how we do this in our app:
App.ProjectsRoute = Ember.Route.extend({
route: 'projects',
collection: Ember.Route.extend({
route: '/',
connectOutlets: function (router) {
router.get('applicationController').connectOutlet({
name: 'projects',
context: App.Project.find()
});
}
})
and then, the handler of creating a project (in the router):
createProject: function (router) {
App.Project.createRecord({
name: 'new project name'.loc()
});
router.get('store').commit();
},
Just for the record: as of today (using Ember Data 1.0.0-beta), the library takes this situation into account. When a record in an array gets deleted, the array will be updated.
If you try to delete an element on that array manually, for example by using .removeObject(object_you_just_deleted) on the model of the containing controller (which is an ArrayController, hence its model an array of records), you'll get an error like:
"The result of a server query (on XXXXX - the model you try to update manually) is immutable".
So there is no need anymore to code by hand the deletion of the record from the array to which it belonged. Which is great news because I felt like using ED and working it around all the time... :)
Foreword
I had a similar problem and found a little tricky solution. Running through the Ember-Data source code and API docs cleared for me the fact that AdapterPopulatedRecordArray returns from the queried find requests. Thats what manual says:
AdapterPopulatedRecordArray represents an ordered list of records whose order and membership is determined by the adapter. For example, a query sent to the adapter may trigger a search on the server, whose results would be loaded into an instance of the AdapterPopulatedRecordArray.
So the good reason for immutability is that this data is controlled by the server. But what if I dont need that? For example I have a Tasklist model with a number of Tasks and I find them in a TasklistController in a way like
this.get('store').find('task',{tasklist_id: this.get('model').get('id')})
And also I have a big-red-button "Add Task" which must create and save a new record but I dont want to make a new find request to server in order to redraw my template and show the new task. Good practice for me will be something like
var task = this.store.createRecord('task', {
id: Utils.generateGUID(),
name: 'Lorem ipsum'
});
this.get('tasks').pushObject(task);
In that case I got announced error. But hey, I want to drink-and-drive!
Solution
DS.AdapterPopulatedRecordArray.reopen({
replace: DS.RecordArray.replace
})
So that's it. A little "on my own" ember flexibility hack.