Adding hasMany items to a post ember not working - ember.js

I'm trying to create a new post and I want to add some tags to that post when I create it.
So let's say I have a list of tag objects in an array.
The hasMany tags relationship is an async one by the way.
Here is my code
var post = store.createRecord('post', {
name: this.get('name'),
slug: this.get('slug'),
description: this.get('description'),
});
post.get('tags').then(function(post) {
post.pushObjects(this.get('selectedTags'));
});
post.save().then(...);
But in the POST request the data that's being sent has tags as a blank array like tags:[]
The tags already exist in the selectedTags variable in the controller. These tags are coming straight from the server.
So I have no clue why this is not working.
If anyone has an idea, that would be great.

the post.get('tags') is an async call, so it will happen after the save in the stated scenario and should be changed to something like this:
var self = this;
post.get('tags').then(function(tags) {
tags.pushObjects(self.get('selectedTags'));
post.save().then(...);
});

Related

Accessing store in another route

I have this model:
App.Game = DS.Model.extend({
name: attr(),
uri: attr()
});
and this route:
App.GamesRoute = Ember.Route.extend({
model: function() {
return this.store.find('game');
}
});
This works fine, calls the backend server, and stores elements in the store (I've checked with Ember inspector). This is the json I return:
{"games":[{"id":"TicTacToe","name":"TicTacToe","uri":"http://localhost:10000/games/TicTacToe"}]}
Now I have this template for 'games' (snipped):
{{#each game in model}}
{{#link-to 'games.matchlist' game.id}}{{game.uri}}{{/link-to}}
This shows the URI for each game. Now in the games.matchlist route what I would like to do is to search in the store by the game_id received param and get the game URI. The reason is that the server doesn't follow RESTAdapter conventions, and I would like to make a custom AJAX query to that URI myself.
This doesn't work:
App.GamesMatchlistRoute = Ember.Route.extend({model: function(params) {
var store = this.store;
var game = store.find('game', params.game_id)
console.log(game);
console.log("URI: " + game.uri);
at this point, game is an object but it's not an instance of my model. It doesn't have a uri attribute. What am I doing wrong? I'm feeling that I'm missing something obvious.
If you want to get records without hitting the server and you know you already have it in the store, use this.store.getById('game', ID).
I'm on my mobile, but you need to create a GameAdapter and customize I believe the fetch function. Checkout the docs for adapters on the ember site and you should have your answer.
Your other option is to fetch the data from your server and use this.store.pushPayload(data).
Docs here: http://emberjs.com/api/data/classes/DS.Store.html#method_pushPayload
And the adapter docs here: http://emberjs.com/guides/models/customizing-adapters/

Ember retrieve last instert id

In every example I see, we always use data like if we knew what was the next ID. I'm building a web application that multiple users will be using at the same time. When a user click on the button "Create a category", I create a new record, send it to my API that saves it in the DB. Unfortunately Ember has no idea of my new Category ID. Let's say he made a typo. He click on "Modify". The app has no idea of the ID and therefor, cannot complete the route :product_id/edit.
I see two solution :
One thing I tried, that would potentially fix my problem is returning the ID in a the header. So I would send my data, return Status 201 (Created) if everything went well, then get the content of the Header and assign the created ID. Looks good on paper, but I have no idea how to get the content of my header with Ember.
App.CategoriesAddRoute = Ember.Route.extend({
setupController: function(controller) {
controller.newRecord();
},
actions: {
save: function() {
controller = this.controllerFor("category");
// Here I'd like to use something like .then( getHeaderContent('id') )
// and assign that value to the category
controller.get('content').save()
this.transitionTo('categories.index');
}
},
});
Reload my categories data with a new query. I would like to avoid that but if I have no other choice I'll go for this.
It's standard practice on save of a new model (POST) to return the model back from the server with the new id. After the server returns the model Ember Data will update the model so the model client side should have the id.

Saving nested models

I have two models like this:
App.Build = DS.Model.extend({
allegiance: DS.attr('string'),
profession: DS.attr('string'),
skills: DS.hasMany('skill')
});
App.Skill = DS.Model.extend({
name:DS.attr('string'),
value:DS.attr('number')
});
In my app, I have controls to set the allegiance, profession, and values of each skill (there's up to 55).
Then in the actions hash of my application controller, I have an action to save the build model to the server.
save:function(){
var store = this.get('store');
var skills = this.get('controllers.skills').get('model');
console.log(skills);
var build = store.createRecord('build',{
profession:1,
allegiance:1,
skills:skills
});
build.set('skills',skills);
build.save();
console.log('Saved!');
}
But when the build model is sent to the server the skills property is an empty array:
{"build":{"allegiance":"1","profession":"1","skills":[]}}
I'm sure I'm doing something wrong, but I can't figure out what and can't find any good documentation about it. An additional note, all I care about submitting is the skill id and value.
Any help will be greatly appreciated!
UPDATE:
Following Daniel's suggestion, I've edited the save function to use pushObjects to put the skills into the Build model, then save it. It's working better now. The generated post data is like this now:
{"build":{
"allegiance":1,
"profession":1,
"skills":["1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55"]}}
That being a list of the skill ids. None of the other attributes are submitted in the post. I've tried iterating over skills, creating a new object, and just pushing in the id and value, which are the only parts I need, but that gives me an error. Something like, can not use undefined, must be type skill.
This seems like something Ember data should handle natively. Is there something I'm missing to get it to send the other skill attributes in the request?
Thanks!!
If anyone else is interested, I solved the issue by overriding the serlizer with a custom serliazer for the Build model like this:
App.BuildSerializer = DS.RESTSerializer.extend({
serializeHasMany: function(record, json, relationship) {
if(relationship.key === 'skills') {
var skills = record.get('skills');
var block = [];
skills.forEach(function(skill, index) {
var current = {};
current.id = skill.get('id');
current.value = skill.get('value')
block[index] = current;
});
json['skills'] = block;
} else {
return this._super(record,json,relationship);
}
}
});
UPDATE:
There's a much easier way to do this now using the DS.EmbeddedRecordsMixin like this:
App.BuildSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin,{
attrs: {
skills: 'records'
}
});
Is the skills model a RecordArray? That's the underlying model Ember data uses. You might try creating the record then using pushObjects after the fact.
var build = store.createRecord('build',{
profession:1,
allegiance:1
});
build.get('skills').pushObjects(skills);
additionally, save returns a promise, so in order to properly handle the successful save versus failure you can handle it like this.
build.save().then(
function(){
console.log('Saved!');
},
function(){
console.log('Failed to save');
});

Loading JSON object into Ember Data, e.g. Embedding JSON into the page on load and populating the store

My application embeds initial data into the html so that Ember does not need to send extra http requests on initialization. I am using the latest Ember data and I have not been able to succesfully take a JSON object, which is the same as Active Model Serializer returns when you save or create a record, and load it into the store.
I am currently trying these methods with no success:
In a route -
this.get('store').load(App.Post, data)
and
this.get('store').loadMany(App.Post, data)
I also use Pusher that sends me the JSON (generated by Active Model Serializer) for an updated object and the callback in my route currently looks like this.
refresh: function(data) {
var json = data
var store = this.get('store')
var type = App.Post
var id = data.reply.id
Ember.run(this, function(){
store.adapterForType(App.Post).didFindRecord(store, type, json, id);
});
}
Has anyone successfully done this? I know Discourse is not using Ember Data so their solution is different. I really appreciate any help in the matter. Thanks
These questions have both been discussed elsewhere. I'm going to give you pointers to the other discussions, so that you can follow along with the conversation as people improve the current answers:
For loading data manually, see this StackOverflow post.
For updating data that has changed on the server, see this discuss.emberjs.com post.
actions: {
save() {
let json = this.get('json');
json.id = '123123'; // id is required in order to use store.push
let store = this.get('store');
this.set('myModel', store.push(store.normalize('myModel', json)));
}
}

add/delete items from Ember Data backed ArrayController

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.