I want use Backbone.save the model,and the model's nest data need to be filter,so i use
model.save(null,{
success: ...,
error:...,
data: {
id:null,
name:'myname',
nestmodel: {
id:'xx'/*Other data i don't need it,so just id column*/
}
}
}
And I don't want to use patch HTTP METHOD. Because i just add a new model,not change part data.
And i don't want to post some nestmodel data,Because it's to big and i just want the id is ok.
And nestmodel just need the id.
I have read Exclude model properties when syncing (Backbone.js) and Backbone.js/express.js parameters for model.save()
There is a way to solve that problem.
That's whole my code:
sync: function(method, model, options) {
var data, orderSuiteItems;
if (method === 'create') {
options.url = this.url;
} else {
// MUST setting the url .options's url is undefined
options.url = this.url + this.idUrl(this.get('id'));
}
// IF `create` or `update` , pick the we need properties
if (method === 'create' || method === 'update') {
orderSuiteItems = [];
if (this.has('orderSuiteItems')) {
// Because the `dishes` and `dishesUnitPrice` have a lot of prop,
// Each of `dishes` or `dishesUnitPrice` may each have 10K data
orderSuiteItems = _.map(this.get('orderSuiteItems'), function(osi) {
return {
id: osi.id,
qty: osi.qty,
servingQty: osi.qty,
confirmQty: osi.confirmQty,
deleted: osi.deleted,
orderItem: _.pick(osi.orderItem, 'id'),
dishes: _.pick(osi.dishes, 'id'), // HAVE a large prop
dishesUnitPrice: _.pick(osi.dishesUnitPrice, 'id'), // HAVE a large prop
orderItemStatus: osi.orderItemStatus,
piece: osi.piece
};
});
}
data = {
id: this.get('id'),
order: this.get('order'),
price: this.get('price'),
dishes: _.pick(this.get('dishes'), 'id', 'isSuite'),
dishesUnitPrice: _.pick(this.get('dishesUnitPrice'), 'id'),
qty: this.get('qty'),
servingQty: this.get('servingQty'),
confirmQty: this.get('confirmQty'),
sum: this.get('sum'),
orderSuiteItems: orderSuiteItems,
orderItemStatus: this.get('orderItemStatus')
};
// Setting attrs with pick data.
options.attrs = data;
return Backbone.sync(method, model, options);
} else {
return Backbone.sync(method, model, options);
}
}
I hope you just put the data option for the sake of the example's clarity.
Anyway, how about using unset to remove your attribute just before using Model#save? Re-set it just afterwards.
Another solution would be to override the Model#save method.
You could also shadow the same method by defining it as a property and not in the prototype (that'd give you the opportunity to switch back).
Solution #1 or something similar would be the easiest. Solution #2 may be more, let's say, risky, but would have maybe less boilerplate. I would use the #3 only in some very specific case (can't even think about one as of now) that would include: object being a singleton (because we're not using the prototype)(or only in a limited number), need to switch the 2 modes a lot, better to have only 1 method.
Edit:
Solution #1:
var nestedModel = myModel.get('nestmodel');
myModel.save('nestmodel', nestedModel.id, {silent: true});
myModel.set('nestmodel', nestedModel, {silent: true});
I added the silent flag as I don't know if you're listening to your nestmodel attribute's changes. I'll add code for the other solutions if this one doesn't suit you.
Related
I am trying to take benefits of model instance methods, as stated in the doc. So I defined User class as following:
class User extends Model {
addRole(role){
let roles = this. roles;
roles[role] = true;
this.roles = roles;
this.save();
}
removeRole (role) {
let roles = this.roles;
delete roles[role];
this.save();
}
hasRole (role){
return this.roles[role] != null;
}
}
User.init({
// some attributes
,
roles:{
type: DataTypes.JSON,
allowNull: false,
}
}, { sequelize});
I expected to use methods addRole(), removeRole() and hasRole() in any User instance.
The problem that all the methods can't save their changes to database. (Can read only!)
// example
let user = null;
// get the first user to test.
User.findAll()
.then(users =>{
user = users[0];
user.addRole("admin");
console.log(user.roles); // {admin: true}
user.save();
// However the changes don't appear in the database.
});
I had found the answer.
For some reasons, sequelise can't detect the changes of the json object properly. As sequelise is optimised internally to ignore call to model.save() if there is no changes of the model. So, sequelize randomly ignore the save method.
This behavior had no relation with instance method as I believed when I face this problem first time.
To get out of this problem, I had to use :
user.addRole("admin");
user.changed("roles", true); // <<<< look at this;
console.log(user.roles); // {admin: true}
user.save();
Please note that this function will return false when a property from a nested (for example JSON) property was edited manually, you must call changed('key', true) manually in these cases. Writing an entirely new object (eg. deep cloned) will be detected.
Example:
const mdl = await MyModel.findOne();
mdl.myJsonField.a = 1;
console.log(mdl.changed()) => false
mdl.save(); // this will not save anything
mdl.changed('myJsonField', true);
console.log(mdl.changed()) => ['myJsonField']
mdl.save(); // will save
changed method usage
I'm trying to represent multiple selects with its selected values from backend JSON to knockout view model.
And it's needed to retrieve this JSON when each select is changed, first time - all is ok, but if I apply mapping again (ko.mapping.fromJS(test_data, ViewModel)), all subscriptions are lost does anyone know how to avoid this situation?
jsfiddle (I don't know why selects don't have its values, without jsfiddle - all is ok):
http://jsfiddle.net/0bww2apv/2/
$(ViewModel.attributes()).each(function(index, attribute) {
attribute.attribute_value.subscribe(function(name) {
console.log('SUBSCRIBE', name);
var send_data = {};
$(ViewModel.attributes()).each(function (index, attribute) {
send_data[attribute.attribute_name.peek()] = attribute.attribute_value.peek();
if (attribute.attribute_value() === null) {
send_data = null;
return false;
}
});
if (send_data) {
console.log('REQUEST TO BACKEND: ', ko.toJSON(send_data));
ko.mapping.fromJS(test_data, ViewModel);
// subscriptions is lost here !
}
});
});
At last I've solved my own question with knockout.reactor plugin,
If we remove all auxiliary constructions, it will look like:
var ViewModel = ko.mapping.fromJS(test_data);
ko.applyBindings(ViewModel);
ko.watch(ViewModel, { depth: -1 }, function(parents, child, item) {
// here we need to filter watches and update only when needed, see jsfiddle
ko.mapping.fromJS(test_data2, {}, ViewModel);
});
This way we update selects and don't have troubles with subscription recursions.
full version (see console output for details): http://jsfiddle.net/r7Lo7502/
In the code below, the fetch() and sync() methods are not doing anything.
I am trying to see how the data in my localStorage gets updated and the methods are not updating it (example LS string is in the code)
Where am I going wrong?
function makeWorkingLS(collDesc, projDesc, Id, Description, ElapsedSeconds, ElapsedTime, WorkItemType){
//Create observable object from params
var activeTaskObject = kendo.observable ({
client: collDesc,
project: projDesc,
taskId: Id,
description: Description,
elapsedSeconds: ElapsedSeconds,
elapsedTime: ElapsedTime,
comment: WorkItemType
});
// example string in localStorage:
//{"client":"Morken Mindy","project":"Shazbat creation engine","taskId":183,"description":"Create the Shazbat 100% efficiency engine","elapsedSeconds":296803,"elapsedTime":"82h43m","comment":"Task"}
// Convert to JSON string for localStorage
var activeTask = JSON.stringify(activeTaskObject);
console.info(activeTask);
//Write to localStorage
window.localStorage.setItem("activeTask",activeTask);
//Set it as the active datasource for updating to webservice
var activeTaskDS = new kendo.data.DataSource({
transport: {
read: function(options){
taskItem = JSON.parse(localStorage["activeTask"]);
},
update: {
url: remUpd, //url var declared earlier in the process
dataType: "json"
}
},
schema: {
model: {
client: "client",
taskId: "taskId"
},
data: function(){
return taskItem;
}
}
});
activeTaskDS.fetch(function(){
activeTaskDS.data()[0].set("client", "NOBODY");
activeTaskDS.sync();
cosole.log("activeTaskDS.data()[0] : "+activeTaskDS.data()[0]); //should read 'NOBODY' but reads 'Morken Mindy'
});
}
Thanks in advance,
Neil.
I'm not sure what is the problem actually, but I have to point some important things:
AFAIK, when you customize any transport methods you have to pass the data into a callback in the options object:
transport: {
read: function(options){
taskItem = JSON.parse(localStorage["activeTask"]);
// Tells the widget to handle that collection
options.success(taskItem);
}
}
In schema.data it seems that you want to pass your data through this method(correct me if I'm wrong). But this method isn't for that purpose. It is used just to tell the widget which field to read(in case of passing a string to it) or to read a property from a response, which comes as a parameter that you are not using. Check the second example here. So this may not be right way to read the taskItem object as data;
Speaking about the taskItem object, it seems that its the base data of your dataSource but it isn't defined(at least on the snippet you posted). What I mean is, if you follow the step 1 you won't even need to read from that object no more.
Please let me know if this is helpful and if you need anyting more.
When running the following from the UserController on Google Chrome, with ember-couchdb-kit-0.9, Ember Data v1.0.0-beta.3-56-g8367aa5, Ember v1.0.0, and this couchdb adapter:
customerSignUp: function () {
var model = this.get('model');
var customer = this.get('store').createRecord('customer', {
description: 'Why hello sir',
user: model
});
customer.save().then(function() {
model.set('customer', customer);
model.save();
});
}
with these models:
App.User = App.Person.extend({
name: DS.attr('string'),
customer: DS.belongsTo('customer', {async: true })
App.Customer = DS.Model.extend({
user: DS.belongsTo('user', {async: true}),
description: DS.attr('string')
});
neither the user nor the customer has their relationship set properly (in the Ember Debugger the user has null and the customer has <computed>, rather than some sort of <EmberPromiseObject> which is what they have when it works).
This only happens when the object in question is persisted. If the save() calls are omitted, both have correctly set relationships, but of course the database hasn't been updated with this information. Whenever the saves happen, the relationships are overwritten with empty entries.
I found that the problem was in the adapter's serializeBelongsTo function, which I've now changed my copy to the following:
serializeBelongsTo: function(record, json, relationship) {
console.log("serializeBelongsTo");
console.log(record.get('user'));
console.log(json);
console.log(relationship);
var attribute, belongsTo, key;
attribute = relationship.options.attribute || "id";
console.log(attribute);
key = relationship.key;
console.log(key);
belongsTo = Ember.get(record, key);
console.log(belongsTo);
if (Ember.isNone(belongsTo)) {
return;
}
json[key] = Ember.get(belongsTo, attribute);
console.log(Ember.get(belongsTo, attribute));
console.log(json);
if (relationship.options.polymorphic) {
return json[key + "_type"] = belongsTo.constructor.typeKey;
}
else {
return json;
}
}
attribute, belongsTo, and key all log as correct, but
console.log(Ember.get(belongsTo, attribute)); returns undefined,
which I've tried to change to
console.log(Ember.get(Ember.get(belongsTo, 'content'), attribute));
since console.log(belongsTo); told me the id attribute was hidden inside a content object. Attached is a screenshot showing what I mean.
The change doesn't fix the problem though, and I keep getting undefined. No matter what method I use to try to get the id out of the belongsTo object, I always get either null or undefined. Here are some examples of things I've tried to get content out of the object:
var content = belongsTo.content;
var content = Ember.get(belongsTo, 'content');
var content = belongsTo.get('content');
console.log(json); returns Object {description: "Why hello sir", user: undefined}
Here's a pastebin showing relevant output: http://pastebin.com/v4mb3PJ2
Update
A very confusing update!
When I save the model from a different function:
saveModel: function() {
this.get('model').save().then(
function( data, textStatus, jqXHR ) {
console.log('Saved successfully.');
},
function( jqXHR, textStatus, errorThrown ) {
console.log(jqXHR);
console.log(errorThrown);
console.log(textStatus);
}
);
}
The model is correctly saved. Everything in serializeBelongsto works exactly as expected.
Here's a different pastebin showing output for this case: http://pastebin.com/Vawur8Q0
I figured out the problem. Basically the belongsTo object in serializeBelongsTo wasn't really resolved by the time it was being referenced, which I found out by querying isFulfilled. So I implemented by saving side this way:
function saveOn (target, attribute) {
target.addObserver(attribute, function () {
if (target.get(attribute)) {
console.log("Inside with %#".fmt(attribute));
target.removeObserver(attribute);
Ember.run.once(target, function() {
target.save();
});
}
});
};
customerSignUp: function () {
var model = this.get('model');
var customer = this.get('store').createRecord('customer', {
description: 'Why hello sir'
});
customer.save().then(function () {
model.set('customer', customer);
customer.set('user', model);
saveOn(customer, 'user.isFulfilled');
saveOn(model, 'customer.isFulfilled');
});
}
Now everything works like a charm. It might be a good idea for serializeBelongsTo to take this into account though. This line: console.log(Ember.get(belongsTo, 'isFulfilled')); was coming up false in my case. There was just a race condition of some sort between the creation of the record and it's serialization!
I'd like to make my saveOn function return a promise though, which I could then use to chain multiple saveOns together. That way I wouldn't have to do a customer.save() to make sure the id's were populated.
I am working with mongoLab and the model id looks like this
"_id": {
"$oid": "50f9a0f5e4b007f27f766cf3"
},
I am using the idAttribute to set the model id to _id and everything works fine until I attempt to update the model.
Because the _id attribute exists in the model, I am getting an error when I attempt to insert.
Do I need to remove the attribute _id from my attributes? I was under the assumption that the magic of Backbone would clean up the attributes appropriately
You would need to remove the _id attribute.
In the MongoLab REST API, the id isn't part of the data payload itself, but that isn't the case for all backends. It probably makes more sense for Backbone to assume that the id should be present in the payload, than it would to assume it should not.
That being said there's no real nice way to get Backbone to clean the id from the payload automatically. Your best bet without monkeypatching/rewriting too much of the code would probably be to override Model#toJSON, something akin to:
Backbone.Model.prototype.toJSON = function (options) {
var attrs = _.clone(this.attributes);
// In this case you'd have to pass `includeId: true` to `toJSON` when you
// actually *want* the _id in the output.
return options && options.includeId ? attrs : _.omit(attrs, '_id');
};
You could also monkeypatch sync, something like:
var sync = Backbone.sync;
Backbone.sync = function (method, model, options) {
options || (options = {});
// if options.attrs is present, Backbone will use it over dumping toJSON
if (!options.attrs) options.attrs = _.omit(model.attributes, '_id');
return sync.call(Backbone, method, model, options);
};
had the same issue where _id translated to null when in javascript..
had to do something like..
var myModel = Backbone.Model.extend({
parse: function(response){
var response = response.whatever;
response.id = response.null;
delete response.null;
return appointment;
}
});
or for a collection
systems.forEach(function(system){
console.log(system);
system.id = system.null;
delete system.null;
});