Creating Child Records in hasMany Relationship with Ember.Js - ember.js

I haven't found a satisfactory answer through my search, so I figured I'd ask here.
I'm currently using Ember.Js, Ember-Data, and Ember-Firebase-Adapter, and attempting to create a CRUD application which will create a Parent Record, and then subsequent Child Records to said Parent Records.
(note that DS.Firebase.LiveModel is the Firebase adapter equivalent of DS.Model/Ember.Model)
Here are my models, altered to be generic Post/Comment types
App.Post = DS.Firebase.LiveModel.extend({
name: DS.attr('string'),
body: DS.attr('string'),
date: DS.attr('date'),
comments: DS.hasMany('App.Comment', {embedded: 'always'})
});
App.Comment = DS.Firebase.LiveModel.extend({
message: DS.attr('string'),
timestamp: DS.attr('string'),
post: DS.belongsTo('App.Post', {key: "post_id"})
});
(Should my post_id = post?)
And here is my route for creating Comments:
App.PostsCommentRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', App.Comment.find());
}
});
Here's my controller for the PostsCommentRoute:
App.PostsCommentController = Ember.ObjectController.extend({
newMessage: null,
newTimestamp: null,
saveComment: function() {
App.Pbp.createRecord({
message: this.get('newMessage'),
timestamp: this.get('newTimestamp')
})
App.store.commit();
this.set('newMessage', null);
this.set('newTimestamp', null);
}
});
I think I may be missing the serializer? And I've read several things on addArray but the things I tried to plug in did not prove fruitful. Because my comments create fine, however they are not associated to the post in anyway in my JSON.
Is there a way for the created Comments to find the related Post_Id and then associate to said Post when created? So my Post JSON has an array of Comment_Ids which then allows them to be displayed with the post?
Any help, or links with good examples would be much appreciated. I know this is a relatively simple quandary yet I've been stuck on it for some time now :p

What you can try and do is this
post = App.Post.find(1);
post.get('comments').pushObject(App.Comment.createRecord({})); //This will add a new comment to your post and set the post_id as the post id
App.store.commit()
Hope it helps

Related

Ember Data Endpoint Issue

I am experiencing a weird issue while using ember data. With the following user model everything works great.
App.User= DS.Model.extend({
firstName: attr(),
lastName: attr()
});
I call user.save() and is posts to /users with the correct data. However when i try and use a user model that has relationships on it
App.User= DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string'),
friends: DS.hasMany('user'),
followers: DS.hasMany('user'),
});
For some reason with that model when i call user.save() it posts to /Users (note the capitalization. Also, in the response it expects it formatted {"User": {...}} instead of {"user": {...}}
Anyone run into this before? I could always add the additional endpoints to my api however I would like it to work uniform if possible.
I did a little more digging and it seems when you add a relationship to the model there is a computed property called relationshipsByName. This property, in my example, will set the meta.type property to 'User'. It works without relationships because I called the createRecord method with 'user' so i assume it uses this as the type. When the relationship is added it uses 'User'
I found that modelFor calls the resolvers normalize on the keys. So the solution is to add a custom resolver like below.
App = Ember.Application.create({
Resolver: Ember.DefaultResolver.extend({
normalize: function(fullName) {
var n = this._super(fullName);
if(fullName.startsWith('model')){
n = n.replaceAt(6, n[6].toLowerCase());
}
return n;
}
})
});
*note i have string extensions for startsWith and replaceAt

Ember Data: can't get a model from data store

I have tried to populate a template with Ember Data.
I'm getting a weird problem when I try to find a model inside my DS Store.
I've followed some tutorials but got an irritating error.
The error is 'Error while loading route: undefined'.
What I've tried:
MovieTracker.Store = DS.Store.extend({
url: 'http://addressbook-api.herokuapp.com'
});
MovieTracker.Contact = DS.Model.extend({
first: DS.attr('string'),
last: DS.attr('string'),
avatar: DS.attr('string')
});
MovieTracker.Router.map(function() {
this.resource('contacts');
});
MovieTracker.ContactsRoute = Ember.Route.extend({
model: function(){//works when changing to 'activate:'
//return; //this works! it shows me a simple template and updates URL to index.html#/contacts
return this.store.find('contact');//error: 'Error while loading route: undefined'
}
});
In the Index.html I have a simple #link-to to 'contacts' (application handlebar), it works well.
I have also a simple template called contacts, which works fine when I give up the this.store.find('contact') line.
JSBin: http://emberjs.jsbin.com/OxIDiVU/170/edit?html,js,output
The JSON is in: http://addressbook-api.herokuapp.com/contacts
Can you please give me any advice?
Would you prefer Ember Data at all (1.0 Beta 5).
Another question: a website without precompiling the handlebars is not gonna be a good idea?
Thank you a lot for reading!
When defining the host you define that on the adapter, not the store.
MovieTracker.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'http://addressbook-api.herokuapp.com'
});
Additionally, you shouldn't define the id on the model, it's there by default
MovieTracker.Contact = DS.Model.extend({
first: DS.attr('string'),
last: DS.attr('string'),
avatar: DS.attr('string')
});
http://emberjs.jsbin.com/OxIDiVU/172/edit
And the newer versions of ember data aren't documented on the website yet, but the transition document should help explain some of the nuances and changes.
https://github.com/emberjs/data/blob/master/TRANSITION.md

performing rollback on model with hasMany relation

I have models defined as :
App.Answer = DS.Model.extend({
name: DS.attr('string'),
layoutName: DS.attr('string')
});
App.Question = DS.Model.extend({
name: DS.attr('string'),
answers: DS.hasMany('answer', {async: true})
});
I have a component that allows for deleting and adding answers to question model. The component comes with apply and cancel button and when the user clicks on cancel, I wanted all the changes(adds/deletes of answers) to be reverted. Currently rollback doesn't do the trick, I event tried model.reload() when using rest adapter and that didn't work for me either. Any idea how I can go about doing a rollback in this situation?
When using the rest adapter, I pretty much fall to the issue pointed here : EmberJS cancel (rollback) object with HasMany
Thanks, Dee
UPDATE :
Since I couldn't perform rollback the intended way, I performed these steps:
1) get all the answers back from the server
2) remove answer association from the question
3) manually add answer association to the question model from data received from server
This seems to be working well BUT sadly I am getting this one error that I cannot shake off.
Here is a jsbin of updated progress: http://jsbin.com/uWUmUgE/2/
Here you can create new answer and then append it to question and do rollback too. BUT, if you follow these steps, you will see the issue I am facing:
1) delete an answer
2) add an answer
3) perform rollback
4) add an answer
It throws this error:
Error: Attempted to handle event didSetProperty on while in state root.deleted.uncommitted. Called with {name: position, oldValue: 1, originalValue: 1, value: 2}.
I will super appreciate any help you can provide.
WORK-AROUND:
One simple workaround was to just hide the answers on delete. I modified the model a bit like:
App.Answer = DS.Model.extend({
name: DS.attr('string'),
layoutName: DS.attr('string'),
markToDelete: DS.attr('boolean', {default: false})
});
And my rollback function had this logic:
answers.forEach(function (answer) {
if(!answer.get('id')){
//newly created answer models that has not persisted in DB
question.get('answers').removeObject(answer);
answer.deleteRecord();
} else {
answer.rollback();
}
});
I'm not sure of your scope but for this relationship (I'm actually rolling back the belongsTo here but I'm curious if this helps in any way)
App.Appointment = DS.Model.extend({
name: DS.attr('string'),
customer: DS.belongsTo('customer', {async: true})
});
App.Customer = DS.Model.extend({
name: DS.attr('string'),
appointments: DS.hasMany('appointment', {async: true})
});
I'm able to rollback both the appointment and it's hasMany customer model like so (from within my route)
App.AppointmentRoute = Ember.Route.extend({
actions: {
willTransition: function(transition) {
var context = this.get('context');
var dirty =context.get('isDirty');
var dirtyCustomer=context.get('customer.isDirty');
var message = "foo";
if ((dirty || dirtyCustomer) && confirm(message)) {
transition.abort();
}else{
context.get('customer').get('content').rollback();
context.rollback();return true;
}
}
});

How to "fork" a model in Ember Data

I'm not sure if it's the correct way to express my requirement. But the word "fork" appears in the roadmap of Ember Data github page. And it's a killer feature in EPF. I'm wondering if I can do it in Ember Data.
The fork feature is useful when we have an edit page and bind a model. When we edit the information, I don't want the model properties to be changed because if the model properties are also displayed in other place they will be changed automatically. That's not what I want.
An example is a list on the left side of the page and a edit form for a specific model on the right side of the page. When I modify role name in the text field, the role name on the left side is changed because of data binding.
EPF solves this problem by "fork" the existing model and set it in a child session. The session in EPF is similar with store in Ember Data. When you modify the forked model it does not effect the model in the main session. After the forked model is updated it can be merged back to main session, and the corresponding model in main session are updated.
What I can think a solution in Ember Data is to create a different store and copy the model to that store. But it is a bit complicated. Does anyone have a better solution? I searched on stackoverflow and ember discuss forum and didn't find an answer.
I'm not sure if there is a standard or common way to do this in Ember, but I have written a Mixin that I can put on my routes to give some basic 'buffering' of the model:
App.BufferedRouteMixin = Ember.Mixin.create({
setupController: function(controller, model) {
this.setBufferFromModel(controller, model);
this._super(controller, model);
},
setBufferFromModel: function(controller, model) {
var buffer = {};
controller.set('model', model);
model.eachAttribute(function(name, meta) {
buffer[name] = model.get(name);
});
controller.set('buffer', buffer);
},
setModelFromBuffer: function() {
var model = this.get('model'),
buffer = this.get('buffer');
model.eachAttribute(function(name, meta) {
if (buffer[name]) {
model.set(name, buffer[name]);
}
});
}
});
Once this is added to my Edit Route, I can call setModelFromBuffer in my save action. In my templates, I can use the {{#with buffer}} helper.
What I believe to be the simplest solution, is to have an Ember.Object that mimics the structure of your model. When entering an edit mode, copy the properties from the model to the Ember.Object and then have them update there until the user clicks 'Save' or whichever action you wish to merge the changes back in. One thing I did that was important was to add the mixin Ember.Copyable to my object. Below is some code I used to solve this issue for myself.
NOTE:: This code was to prevent a model from being created before it was submitted, so instead of edit, mine is create new.
App.SomeModel = DS.Model.extend({
user: DS.belongsTo('user'),
home: DS.belongsTo('home'),
cost: DS.attr('number'),
title: DS.attr('string'),
description: DS.attr('string'),
category: DS.attr('number'),
categoryName: function () {
return Roomy.enums.TransactionCategories[this.get('category')]
}.property('category'),
date: DS.attr('date', {
defaultValue: function() { return new Date(); }
}),
fuzzyDate: function () {
return moment(this.get('date')).fromNow();
}.property('date'),
split: DS.attr('boolean'),
contributors: DS.attr('array'),
points: DS.attr('number')
});
App.SomeModelNew = Ember.Object.extend(Ember.Copyable, {
user: null,
home: null,
cost: null,
title: null,
description: null,
category: null,
date: new Date(),
split: false,
contributors: [],
points: null,
copy: function () {
return this.getProperties('cost', 'title', 'description', 'category', 'date', 'split', 'contributors', 'user', 'home');
}
});
Then to save this model I did something like this.
NOTE:: The code with User and Home I had to use because of the relationships, simply copying the json form of the User and Home would not persist the relationship and give the model the ID's it needed in the database.
Contoller code below:
//Before this function is called, all the inputs in the form have been filled in and the instance now has values for all the fields that were defined for it
saveTxn: function (txn) {
// txn is the App.SomeModelNew instance
copy = this.store.createRecord('transaction', txn); // this returns the App.SomeModelNew.copy() object
copy.set('user', txn.get('user')); // OVerwrite user for relationship
copy.set('home', txn.get('home')); // Overwrite home for relationship
return copy.save();
}
I hope this helps.

How to get model properties

I have model:
App.Item = DS.Model.extend({
itemId: DS.attr('string'),
itemName: DS.attr('string'),
itemType: DS.attr('string'),
});
I successfully create some items from JSON. I can put them to page by {{#each items}}{{ itemName}}{{/each}}. But I don't know, how to get itemName in javascript.
I tried this:
var item = App.Item.find(1);
console.log(item.itemName);
--> undefined
I can't find anything useful from emberjs and ember-data docs. Can anyone help me?
Thanks
I tried this:
var item = App.Item.find(1);
console.log(item.itemName);
--> undefined
This is normal because the call to .find(1); is asyncronous and returns a promise and not the item you are expecting.
Therefore you should try:
App.Item.find(1).then(function(result) {
console.log(record.get('itemName'));
});
It also depends from where you are doing App.Item.find() if it's from inside a route you should wait until the afterModel hook is called to access your items:
App.FooRoute = Ember.Route.extend({
model: function() {
return App.Item.find(1);
},
afterModel: function(record) {
console.log(record.get('itemName'));
}
});
Also be aware that if you where calling find() without parameter then you will receive a RecordArray which you need to loop over to get access to your items. Also worth mentioning is that in ember you should always use .get() and .set() instead of the vanilla dot-notation otherwise you hijack the binding mecanism resulting in no updates in your view etc.
Note, if you are using the latest ember.js release (1.0.0) then the call to .find() should be made somewhat different. But that's not clear from your question.
Hope it helps.