ember-data: Updating a record with a many-to-many relationship? - ember.js

I have a File and a Tag model, which are in a many-to-many relationship:
App.File = DS.Model.extend
name: DS.attr('string')
tags: DS.hasMany('tag')
App.Tag = DS.Model.extend
name: DS.attr('string')
files: DS.hasMany('file')
I'm reading the data as JSON generated via Rails backend, and so far everything works fine. I'm trying to write a tagFile function in my Ember route that tags a file with a specific tag. This works fine on the ember side but of course it doesn't persist, and I can't figure out how to send the proper HTTP request to the Rails backend. So I have
App.FilesRoute = Ember.Route.extend
...
actions:
tagFile: (file, tag) ->
file.get('tags').pushObject(tag)
tag.get('files').pushObject(file)
# file.save() ? or something similar?
I've tried file.save(), which sends a PUT request, but the sent params hash in Rails does not include the Tag record at all. I set a breakpoint in the updateRecord() method inside ember-data, and examined the data variable before it gets sent via AJAX, and this variable does not include the Tag record either.
What is the correct way to send an HTTP request (PUT or otherwise) where I can pass along my child record into the params hash? Is there a way to pass arbitrary data using ember-data methods? I could write my own AJAX call but then it seems like I'm not taking advantage of ember-data. Any insights?
Using: Ruby 2/Rails 4 backend, Ember 1.3.0-beta.1+canary.48513b24, Ember-data 1.0.0-beta.4+canary.c15b8f80

file.get('tags').pushObject(tag);
tag.save();
file.save();
Dual hasMany will connect them together http://emberjs.jsbin.com/OxIDiVU/94/edit
If you want to create a custom serializer for one of the types to include the other. Here's a slightly modified one from ED
App.FileSerializer = DS.RESTSerializer.extend({
serialize: function(record, options) {
var json = {};
if (options && options.includeId) {
var id = get(record, 'id');
if (id) {
json[get(this, 'primaryKey')] = get(record, 'id');
}
}
record.eachAttribute(function(key, attribute) {
this.serializeAttribute(record, json, key, attribute);
}, this);
var tags = [];
record.eachRelationship(function(key, relationship) {
if (relationship.kind === 'belongsTo') {
this.serializeBelongsTo(record, json, relationship);
} else if (relationship.kind === 'hasMany') {
record.get(relationship.key).forEach(function(tag){
tags.push(tag.toJSON());
});
//this.serializeHasMany(record, json, relationship);
}
}, this);
json.tags = tags;
return json;
}
});
http://emberjs.jsbin.com/OxIDiVU/95/edit
Or a more specific serializer
App.FileSerializer = DS.RESTSerializer.extend({
serialize: function(record, options) {
var json = {},
tags=[];
json.id = record.get('id');
json.name = record.get('name');
record.get('tags').forEach(function(tag){
var tagJson = {};
tagJson.id = tag.get('id');
tagJson.name = tag.get('name');
tags.push(tagJson);
});
json.tags = tags;
return json;
}
});
http://emberjs.jsbin.com/OxIDiVU/96/edit

Related

What is the correct way to `push` a updated model back to store?

I am trying to push the updated model back to store. i tried with couple of ways still getting failed.
Please help me to understand to push the model back to store without updating backend api.
here is my try:
import Ember from 'ember';
export default Ember.Route.extend({
model: function(params) {
if(this.store.hasRecordForId('card-list', params.id)){
return this.store.peekRecord('card-list', params.id );
}
},
actions:{
formValidateBeforeNext:function(){
var model = this.controllerFor(this.routeName).get('model');
var modelId = this.controllerFor(this.routeName).get('model').get("id");
var oneTimeFee = this.controllerFor(this.routeName).get('model').get("oneTimeFee");
var monthlyInstalmentAmount = this.controllerFor(this.routeName).get('model').get("monthlyInstalmentAmount");
var updatedModel = JSON.parse(JSON.stringify(model));
updatedModel.type="card-list";
updatedModel.id="13";
console.log( "model would be:-" , updatedModel );
//sending just a updated model fails
let itemModel = this.store.push({'card-list': model });
//after stringfy trying to update still fails
let itemModel = this.store.push({'data': updatedModel });
// this.store.pushObject(JSON.parse(JSON.stringify(model)));
console.log( "store data", this.store.peekRecord('card-list', modelId ) )
this.transitionTo('cs2i.balance.balanceReview', {id:modelId});
}
}
});
What is wrong here? what is the correct way to put back the mode with updates?
UPDATE:Error added
Expected an object in the 'data' property in a call to 'push' for undefined, but was instance
Error
push method will expect the data in the expected format. for eg, if you are using the JSONAPI. the below is the expected one.
store.push({
data: {
// primary data for single record of type `Person`
id: '1',
type: 'person',
attributes: {
firstName: 'Daniel',
lastName: 'Kmak'
}
}
});
You can convert json payload into the expected form by doing so,
store.push(store.normalize('person', data));
If you are having raw JSON data, then you can try pushPayload.
this.get('store').pushPayload('card-list',data);
Refer EmberData Model Maker to know expected result format.
Read ember guides models/pushing-records-into-the-store
Read push API doc
Read pushPayload doc -

Ember data side loading with JSONAPI

The request generated for my route is http://api.myApp.com/tags/123/products, but I need to do some side loading to improve performance, the desired XHR would be:
http://api.myApp.com/tags/123/products?include=sideload1,sideload2,sideload3
My router looks like this:
this.route('tags', function() {
this.route('tag', { path: ':id' }, function() {
this.route('products', function() {
});
});
});
I'll like to sideload some async models for products, currently I have:
// app/routes/tags/tag/products.js
model() {
return this.modelFor('tags.tag').get('products');
}
How would I go about adding query params in route?
I'm doing something similar in a project and store.query(type, { query }); has worked for me. http://emberjs.com/blog/2015/06/18/ember-data-1-13-released.html#toc_query-and-queryrecord
Try doing a store.query when defining your model and passing in
{include: "sideload1,sideload2,sideload3"}
Another option could be creating an adapter for your model and using buildURL to add on the query params... However this is a bit hacky and shouldn't be necessary if your API is following the JSON API standards.
App.TagsAdapter = DS.JSONAPIAdapter.extend({
buildURL: function(type, id) {
var baseURL = 'http://api.myApp.com/tags/123/products';
var method = '?include=sideload1,sideload2,sideload3';
return baseURL + method;
}
});

Best Practice for Creating New Record with belongsTo Relationship

I am wondering about the best practice for creating a new record in Ember with createRecord() and then persisting it to the API? Specifically, should Ember's POST request generally be a single JSON that embeds all the model's relationships, or is it customary to POST each relationship individually?
In my code, I'm not able to get a single JSON, so I'm wondering if I'm missing the "Ember Way" or (more likely) I have a mistake in my code?
DETAILS:
Here are the details of my setup. I have two models:
/models/OrgUser.js:
DS.Model.extend({
...
orgPerson: DS.belongsTo('org-person', { inverse: 'org-user', async: true, embedded: 'always' }),
});
/models/OrgPerson.js:
DS.Model.extend({
...
orgUser: DS.belongsTo('org-user'),
})
I'm attempting to create a new user on the "Create New User" page. The route for that page is below. Is this the best place to call createRecord() for my new models?
/routes/org-users/add.js:
Ember.Route.extend({
model: function() {
var orgPerson = this.store.createRecord('org-person');
var orgUser = this.store.createRecord('org-user' );
orgUser.set('orgPerson', orgPerson);
return orgUser;
},
...
}
Using Chrome console to look at the orgUser object after I call set shows no evidence at all that I have added anything to orgUser. The "Ember" tab of Chrome Debug Tools does reflect the relationship, though.
On my "Create New User" page, my input fields all correspond to both OrgUser properties and OrgUser.OrgPerson properties. Here's an example:
/templates/org-users/add.hbs
...
{{input value=username}} // a property of OrgUser
{{input value=orgPerson.firstName}} // a property of OrgUser.orgPerson
...
In my route, when I go to save() Ember Data POSTs only the orgUser JSON with a null value for orgPerson. I'd like it to embed the orgPerson serialized object in the orgPerson property.
/routes/org-users/add.js:
Ember.Route.extend({
...
actions: {
submitForm: function() {
...
this.currentModel.save().then( onSuccess ).catch( onFailure );
...
}
}
});
This results in a POST request with the following body:
{
"orgUser":{
"username":"MyUsername",
"orgPerson":null,
"id":null
}
Note that orgPerson is null. Thanks in advance for any assistance!
UPDATE: Once again, I think I will need to take a fresh look at my serializer. Here's how it's currently defined.
/serializers/application.js:
DS.RESTSerializer.extend({
// Use the default approach to serializing, but add the id property
serialize: function(record, options) {
var json = this._super.apply(this, arguments);
json.id = record.id;
return json;
},
serializeBelongsTo: function(record, json, relationship) {
var key = relationship.key;
key = this.keyForRelationship ? this.keyForRelationship(key, 'belongsTo') : key;
var data = record.get('data');
if (relationship.options.embedded && relationship.options.embedded === 'always') {
json[key] = data[relationship.key] ? data[relationship.key].serialize( { includeId: true } ) : null;
}
else {
json[key] = data[relationship.key] ? data[relationship.key].get('id') : null;
}
if (relationship.options.polymorphic) {
this.serializePolymorphicType(record, json, relationship);
}
}
});
Per #Kingpin2k's comment, there appears to be some ambiguity (and bugs!) on how best to handle serialize() for a belongsTo relationship. My serializer customization above works great for records that are obtained through this.store.find(), but now I need to enable them for createRecord(). Additional suggestions, pointers are welcome!
It's a bug. https://github.com/emberjs/data/issues/1542#issuecomment-49443496
A workaround is to get the async belongsTo record before attempting to save (It tricks Ember Data into initializing it). In your case you could do it in the model hook.
model: function() {
var orgPerson = this.store.createRecord('org-person');
var orgUser = this.store.createRecord('org-user');
orgUser.set('orgPerson', orgPerson);
return orgUser.get('orgPerson').then(function(){
return orgUser;
});
},
So, I finally figured this out. With the release of Ember-Data-1.0.0-Beta.9, http://emberjs.com/blog/2014/08/18/ember-data-1-0-beta-9-released.html, the EmbeddedRecordsMixin has been introduced. This pretty much solves all my issues!
So, I wound up doing the following:
Upgraded to Ember-Data-1.0.0-Beta.9
Deleted my serializeBelongsTo customization from my serializer
I now define a custom serializer for each model using the EmbeddedRecordsMixin as documented at http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html.
This wound up working perfectly, because I get full declarative control over how and when my records are embedded.
Special thanks to #Kingpin2k for helping me realize my serializer was the problem and for the discussion to help me understand the options.

Emberjs - Custom Adapter

I'm quit new into the world of the Adapter in Ember, and I would like to create a custom one to save some data into the webSQL database for the compatible browser.
It might look very simple but I'm stuck at the beginning of this process.
I have this in a separate file :
DS.WebSQLAdapter = DS.Adapter.extend({
dbName: 'testDb',
dbVersion: '1.0',
dbDisplayName: 'Test Db',
dbSize: (2 * 1024 * 1024),
init: function() {
this.db = cont.openDatabase( this.dbName, this.dbVersion, this.dbDisplayName, this.dbSize );
},
createRecord: function( store, type, query, recordArray ) {
console.log(data);
}
})
In my app.js file :
window.App = Ember.Application.create({});
App.ApplicationAdapter = DS.WebSQLAdapter;
And when I do this in my controller :
App.ApplicationController = Ember.ArrayController.extend({
actions: {
myAction: function() {
this.store.createRecord('someDB', {key: 'test', title: 'myTitle'});
}
}
})
It says that it couldn't found the model someDB.. Do I need to create a model as well for my adapter even though I'm using websql to fetch the data ?
It's telling you that SomeDB doesn't exist. Ember is expecting the following to be in your code somewhere:
App.SomeDB = DS.Model.extend({});
If you don't declare a SomeDB model, you can't create a new record of that type. (At least not with the Ember-Data store.)

Duplicate null-id records in ember-data

I'm using ember 1.0 and ember-data 1.0.0 beta 1. I have the following routes and controller to create and save simple notes ('AuthenticatedRoute' is just a custom made route for logged-in users):
App.Note = DS.Model.extend({
title: DS.attr(),
author: DS.attr(),
body: DS.attr(),
createdAt: DS.attr()
});
App.NotesRoute = App.AuthenticatedRoute.extend({
model: function() { return this.store.find('note'); },
});
App.NotesNewRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.store.createRecord('note');
}
});
App.NotesNewController = Ember.ObjectController.extend({
actions: {
save: function() {
var self = this, model = this.get('model');
model.set('author', localStorage.username);
model.set('createdAt', new Date());
model.save().then(function() {
self.get('target.router').transitionTo('notes.index');
});
}
}
});
When I save a new note everything works as expected. But when I navigate away from the notes route and then back into it, the notes list is populated with a duplicate entry. One entry has an id and can be edited, deleted etc, the other has all the data of the first entry except the id attribute is null. It seems to me ember-data keeps the newly created record (that hasn't been committed to the database and thus has no id yet) alive even when the record becomes committed but I am uncertain as to why. When I reload the page, the list is correctly displayed, no duplicates appear. What am I doing wrong?
For the record, I am using mongodb so I use a custom serializer to convert '_id' attributes to ember-data friendly 'id's, essentially copied from here:
App.NoteSerializer = DS.RESTSerializer.extend({
normalize: function(type, hash, property) {
// normalize the '_id'
var json = { id: hash._id };
delete hash._id;
// normalize the underscored properties
for (var prop in hash) {
json[prop.camelize()] = hash[prop];
}
// delegate to any type-specific normalizations
return this._super(type, json, property);
}
});
I should also mention that this problem existed in ember-data 0.13 as well.
It was a stupid mistake in my RESTful server. I was responding to POST requests with a 204 (empty) response instead of what ember-data expected, that is a 201 ("created") response with the newly created record as the payload. This post made me realize it.
It would be nice though to include this information in the official REST adapter documentation.
That is certainly strange behaviour indeed. Unfortunately I'm not able to explain why you're experiencing this, however:
You can use the willTransition callback in the actions object in your Route to ensure that when it is transitioned away from, if NotesNewController's content property is dirty (i.e. has not been persisted yet), it will have its transaction rolled back.
App.NotesNewRoute = App.AuthenticatedRoute.extend({
model: function() {
return this.store.createRecord('note');
},
actions: {
willTransition: function (transition) {
var model = this.controllerFor('notesNew').get('content');
if (model.get('isDirty') === true) {
model.get('transaction').rollback();
}
return this._super(transition);
}
}
});