How to "fork" a model in Ember Data - ember.js

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.

Related

EmberJs using inject.controller to access parent data

I am trying to access from a child template to its parent data
in the previous versions of emberjs this could have be done using the needs keyword:
app.RepositoriesController = Ember.Controller.extend({
needs: "user",
user : Ember.computed.alias("controllers.user");
});
from what I understand with the new ember version this should be done like the following (as the needs keywords became deprecated):
app.RepositoriesController = Ember.Controller.extend({
user: Ember.inject.controller('user')
});
but it doesn't seems to work in my html code I use the following line which is blank:
{{user.name}}
Thanks!
All you need to do is call:
user: Ember.inject.controller()
And ember is smart enough to automatically pick up the correct controller based on the property key.
http://emberjs.com/api/classes/Ember.inject.html#method_controller
I am not sure that it is the best practice but it did the job:
i added into the Route a new function :
setupController: function (controller, model) {
var user = this.modelFor('user');
controller.set('user', user);
Ember.$.getJSON(user.repos_url).then(function(res){
controller.set('model', res);
});
}
usually fetching data should go to model hook so your code above could be written as:
model: function(){
var user = this.modelFor('user');
return Ember.$.getJSON(user.repos_url).then(function(res){
return { user: user, res: res}
});
}
in controller now you can access model like
user: Ember.computed.alias('model.user'),
res: Ember.computed.alias('model.res')

How to clone template's model?

I am new to Ember and developing simple app that interact with user through form. If the user clicks 'reset' i want to reset the model to initial data.
To achieve this, i am cloning the model and set into the controller as 'oldModel'. If the user clicks reset i want to replace the model with oldModel.
jsbin: http://jsbin.com/EJISAne/673/edit
Please suggest me how can i achieve this by following the best practices.
In your setupController , change this
controller.set('oldModel', Ember.copy(model));
to
controller.set('oldModel', Ember.copy(model,true));
The true option is the key here. It will make a deep clone of the object.
Also there was a typo in your template.
<button action 'reset'>Reset</button>
should be
<button {{action 'reset'}}>Reset</button>
Working jsbin.
EDIT : The earlier jsbin was also throwing the assertion. The assertion was thrown because, Ember.Object doesn't implement Ember.Copyable mixin as told in the exception.
In the method App.parseSource
arr.push(Ember.Object.create(item))
can be changed to just,
arr.push(item)
This won't throw any exception as the check for implementation of copy will be done only for instances of Ember.Object
Update jsbin without exception
Ive implemented my reset like this
My ROUTE
The "routeHasBeenLoaded" property now lets user to change routes, and come back to route without losing any data previously inserted. On the other hand no properties have to be set manually after "save, edit"
e.g this.set('property1', []); after save is no needed. All you do is this.set('routeHasBeenLoaded', null);
import RouteResetter from 'appkit/misc/resetmixin';
export default Ember.Route.extend(Ember.SimpleAuth.AuthenticatedRouteMixin, RouteResetter, {
model : function(){
var self = this;
if(Ember.isNone(self.get('controller.routeHasBeenLoaded'))){
return Ember.RSVP.hash({
property1: this.store.findAll('das/dasdasfa'),
property2: [],
});
} else {
return;
}
}
});
Controller on load
routeHasBeenLoaded : null,
init : function(){
this._super();
this.set('routeHasBeenLoaded', true);
},
RouteResetterMixin
export default Ember.Mixin.create({
theModel: null,
theModelFunction : null,
afterModel : function(model){
this._super();
this.set('theModel', model);
this.set('theModelFunction', this.model.bind(this));
},
actions : {
triggerReset : function(){
var self = this;
this.get('theModelFunction')().then(function(resolvedModel){
self.set('controller.model', resolvedModel);
self.set('controller.routeHasBeenLoaded', true);
});
}
}
});
So im storing my inital model as well as model(); which i get in afterModel hook from parameter (model). And on reset, i reset the model to initial date.
I hope this helps.
Would like to see other solutions on that as well.

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.

Temporary Non-Persistent Record with Ember-Data 1.0.0-beta

I'm new to Ember and Ember-data and am deciding whether to use Ember-Data or one of the other persistence libraries. In order to evaluate, I'm experimenting with writing a small Rails-backed app.
One of my routes can be considered similar to the Todo MVC app that is frequently used in examples.
In my template, I have a number of input fields that represent attributes within the model. Furthermore, I also have one element in the model that represents a hasMany relationship.
Models:
App.CompanyModel = DS.Model.extend
company: DS.attr()
desc: DS.attr()
contacts: DS.hasMany('company_contact')
App.CompanyContactModel = DS.Model.extend
firstname: DS.attr()
lastname: DS.attr()
...
Within my controller, I want to be able to create a new CompanyModel record (and by virtue, add one or more contacts models to it), but not have it appear within the controller's instance of the CompanyModel until I'm ready to do so.
Currently, when a user wants to add a new record, I have a component that calls an action in my controller as follows:
#set('new_company',
#store.createRecord('company')
)
This actually works fine, except for one thing. My view has to populate the individual attributes within "new_company", which it does, however, the record is immediately added to the controller's model instance and appears in the list of records; I only want the newly created record to be visible in the table once a particular action has taken place.
Instead of instantiating new_company with createRecord, I could do something like this:
#set('new_company',
Ember.Object.create
companyname: ''
desc: ''
contacts: [
firstname: ''
lastname: ''
]
)
And then do a #store.createRecord('company', #get('new_company')), however, given I've already defined my attributes in the model, it doesn't feel very DRY to me.
I'm using Ember 1.5.0 and Ember-Data 1.0.0-beta.7.
It appears I'm not the first person to have this issue (create temporarty non persistent object in Ember-Data), but it appears that Ember-Data has sufficiently changed to make all of these solutions inoperable.
Thanks for your help!
You're real issue is you're using what's considered a live collection. I'm going to assume in your route you've done something like this:
App.FooRoute = Em.Route.extend({
model: function(){
return this.store.find('company');
}
});
find with no parameters says, hey Ember Data, find me all the records that are company. Well Ember Data shoots off a request to your back-end, then returns store.all('company'). all is a live collection that will always have all the records of that type currently in the store. In your case, you are saying I want to avoid any record that is new. There are a couple of ways to handle this.
Create a static list. (You'll need to manually add/remove objects to/from this list).
App.FooRoute = Em.Route.extend({
model: function(){
return this.store.find('company').then(function(companies){
return companies.toArray();
});
}
});
Example: http://emberjs.jsbin.com/OxIDiVU/641/edit
Create a computed property that only shows records that aren't new
App.FooRoute = Em.Route.extend({
model: function(){
return this.store.find('company');
}
});
App.FooController = Em.ArrayController.extend({
savedRecords: function(){
return this.get('model').filterBy('isNew', false);
}.property('model.#each.isNew')
// shorthand this could be written like this
// savedRecords: Ember.computed.filterBy('model', 'isNew', false)
});
Then in your template you would iterate over the computed property
{{#each item in savedRecords}}
{{/each}}
Example: http://emberjs.jsbin.com/OxIDiVU/640/edit

Creating Child Records in hasMany Relationship with 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