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.
Related
With the Javascript code below, do I need to define the name of my Parse database (_User) I want it to access? At the moment I'm getting 'error code: 141', so I thought maybe it doesn't know what database I want it to query, since I have more than one.
Parse.Cloud.define("test", function(request, response) {
Parse.Cloud.useMasterKey();
var friendRequestId = request.params.friendRequest;
var query = new Parse.Query("FriendRequest");
query.get(friendRequestId, {
success: function() {
response.success("Success!");
},
error: function(error) {
response.error(error);
}
});
});
For those who want to see where I call the PFCloud
[PFCloud callFunctionInBackground:#"test" withParameters:#{#"friendRequest" : friendRequest.objectId} block:^(id object, NSError *error) {
if (!error) {
NSLog(#"Success");
}
else {
NSLog(#"Error");
}
}];
Thanks!
What do you mean, which database you want it to access? You define the class you'd like to query new Parse.Query("class"); So you're querying through the FriendRequest class. Perhaps your class name isn't actually FriendRequest? It's case sensitive.
I recommend that you use Parse Promises rather than callbacks. Also, an alternative to querying for an object based on id is fetching the object based on id. Here's my suggestion for what your cloud code should look like:
**Note, OP requested some additional help in the comments, so this answer is being updated to reflect his other questions.
Parse.Cloud.define("test", function( request, response )
{
Parse.Cloud.useMasterKey() //You only need this if you're, say, editing a user that didn't make this request or accessing an object that users don't have access to
var FriendRequest = Parse.Object.extend("FriendRequeset");
var friendRequestId = request.params.friendRequest;
var friendRequest = new FriendRequest();
var user1;
var user2;
friendRequest.id = friendRequestId;
friendRequest.fetch().then
(
function( friendRequest )
{
user1 = friendRequest.get("user1");
user2 = friendRequest.get("user2");
return Parse.Object.fetchAll([user1, user2]);
},
function( error )
{
response.error("Error fetching FriendRequest: " + error.message); //This will give you a bit more of a description of your errors
}
).then //fetchAll
function( objects )
{
//set up your relations. I've never used relations, but I'm sure you can find some examples
return Parse.Object.saveAll([user1, user2]);
},
function( error )
{
response.error("Error fetching users: " + error.message);
}
).then //saveAll
(
function( objects )
{
response.success("Success!");
},
function( error )
{
response.error("Error saving the users: " + error.message);
}
);
});
Notice I added some output to your error call. That'll help you a lot while debugging down the road. Get used to doing that.
And finally, check your logs from your Parse dashboard. It usually tells you what line you hit an error on. Look for the "on main.js: xxxx:yy" the xxxx is your line number where the error occurred.
edit - I saw the NSLogs from your obj-c code and mistook those for your response.success/error calls from cloud code. If you saw this before I edited it, I totally lied about that probably being your error :p
Using mongodb, and backbone models I am trying to store an input value as an array inside a model?
So at barebones let's say I have a small little form.
<form id="send-message">
<input name="message" class="message"/>
<button type="submit" class="send">Send</button>
</form>
Then in my backbone code for the view that contains this form, I submit this data. I am using socket.io so I have some code thats like this. I don't think posting the full code for this view is necessary, but I will and hopefully this will prevent confusion.
var Marionette = require('backbone.marionette'),
MessagesView = require('./messages'),
UsersListView = require('./users_list'),
socket = io.connect();
module.exports = ChatView = Marionette.ItemView.extend({
template: require('../../templates/chat.hbs'),
events: {
'submit #send-message': 'sendMessage'
},
initialize: function() {
var self = this;
this.messagesView = new MessagesView({ collection: window.App.data.messages });
this.usersListView = new UsersListView({ collection: window.App.data.messages });
socket.on('new message', function(data) {
self.createMessage(data);
});
},
onRender: function() {
this.messagesView.render().$el.appendTo(this.$el.find('.message-content'));
this.usersListView.render().$el.appendTo(this.$el.find('.users-list'));
},
sendMessage: function(e) {
e.preventDefault();
var $message = this.$el.find('input.message');
socket.emit('send message', $message.val());
$message.val('');
},
createMessage: function(data) {
var model = window.App.data.messages.where({username: data.username});
_.each(model, function(model) {
var values = {
message: data.message
}
model.save(values);
});
window.App.core.vent.trigger('app:log', 'Add View: Created a new message!');
}
});
So summed up this just submits the input data to the node server then the server emits a response and triggers createMessage.
My question is I want to capture all these messages and store them as an array inside the model. So the data structure would look kind of like this.
// This represents the ideal structure of the model at barebones
var user = {
username: 'Grant',
message: {message1: "Hey guys", message2: "Michigan State will win the tourney"}
}
Lets take a closer look at the createMessage method.. You see I save the message like below, but I am unsure how to get the messages already saved on that model then append a new message, thus creating an array. I'll leave it like it is now, but I have tried using a .push() function and tried various ways all failing...
createMessage: function(data) {
var model = window.App.data.messages.where({username: data.username});
_.each(model, function(model) {
var values = {
message: data.message
}
model.save(values);
});
window.App.core.vent.trigger('app:log', 'Add View: Created a new message!');
}
The data goes to mongodb so I also have a controller that handles and (I THINK) overrides backbone.sync, but the controller is fired by a router when a PUT request is made app.put('/api/messages/:id', messages.update); Then the update method handles the data, so possibly I could create the array here.
update: function(req, res) {
models.Message.update({ _id: req.params.id }, {message: req.body.message //Somewhere here I could append?}, function(err, message) {
if (err) {
res.json({error: 'Update failed.'});
} else {
res.json(message);
}
});
}
Edit: So thinking about it, I do want to generate an array of objects.. My biggest problem is figuring out how to append a new object and generate a new key?
For example
var user = {
username: 'Grant',
message: {message1: "Hey guys"}
}
A user submits a new message, how do I create an object with a fresh key, I tried using backbone to get the length of the objects, but that got kind of hairy since this is mostly vanilla based now..
What you're trying to make is an object, not an array - that's why .push() doesn't work.
function(value) {
user.message['message' + (Object.keys(user.message).length + 1)] = value;
}
This will add a new value to the object, with the key 'message' + amount of old 'messages'.
I defined a model 'person' (and a collection 'persons') that has two fields - firstName and lastName. There is a view for creating persons and another view for editing existing persons. The only thing you can do with both views is to set the names. The responsible code looks like that:
create view
create: function () {
var form = $(this.el).find("#add_form");
this.model = window.persons.create({
firstName: form.find("#firstname").val(),
lastName: form.find("#lastname").val()
}, {
success: function(){
alert("success");
window.router.navigate("overview", {trigger: true});
},
error: function() {
alert("error");
}
});
}
update view
update: function () {
var form = $(this.el).find("#edit_form");
this.model.set({
firstName: form.find("#firstname").val(),
lastName: form.find("#lastname").val()
});
this.model.save(null, {
success: function(){
alert("success");
window.router.navigate("overview", {trigger: true});
},
error: function() {
alert("error");
}
});
}
The model's validaton logic
validate: function(attrs) {
var errors = [];
if (!attrs.firstName || attrs.firstName.trim() === "") {
errors.push({name: 'firstName', message: '...'});
}
if (!attrs.lastName || attrs.lastName.trim() === "") {
errors.push({name: 'lastName', message: '...'});
}
//alert(errors.length);
return errors.length > 0 ? errors : false;
}
I'm using the localStorage module instead of a rest interface on a server.
Creating a model works as expected - I only can create a new person if there is a firstName and a lastName. In that case, the router forwards me to "overview". Otherwise, nothing happens (except the alert).
Unfortunetaly, editing persons always results in the success-callback, even if I set both names to an empty string. This means that I get forwarded by the router although it shouldn't happen. BUT: the model in the localStorage isn't updated in that case, so somehow the validation is working. The validation logic gets called four times when hitting the save button, the first time it returns an error-array with length > 0, the other three times it returns false.
Why do I always get the success-callback?
edit:
I updated backbone.js and underscore.js to the latest versions and the behavior changed. The success-callback doesn't get called anymore if there is no reason, but the error-callback isn't called either - it happens just nothing in that case.
use isValid() method. Run validate to check the model state.
http://backbonejs.org/#Model-validationError
It seems I misunderstood the error-callback; it doesn't get called if validation errors occur. The pattern looks like this:
model.save({
// ...
}, {
success: function() {
// gets called if EVERYTHING is okay
},
error: function() {
// gets called if XHR errors or similar things occur
// doesn't take validation errors into account
}
});
if (this.model.validationError) {
// this is for validation errors
}
Let me explain my issue, I am trying to populate Ember.Select directly from database.
I have these routes:
this.resource('twod', function() {
this.resource('twoduser', {
path : ':user_id'
});
});
In twoduser, I am displaying a full information about a single user. In that view, I have a Select Box as well, which end user will select and then with a button, he can add the user to a team that he selected from Ember.Select.
I tried to do this,
App.TwoduserController = Ember.ArrayController.extend({
selectedTeam : null,
team : function (){
var teams = [];
$.ajax({
type : "GET",
url : "http://pioneerdev.us/users/getTeamNames",
data : data,
success : function (data){
for (var i = 0; i < data.length; i ++){
var teamNames = data[i];
teams.push(teamNames);
}
}
});
return teams;
}.property()
})
Then in my index.html:
{{view Ember.Select
contentBinding="team"
optionValuePath="teams.team_name"
optionLabelPath="teams.team_name"
selectionBinding="selectedTeam"
prompt="Please Select a Team"}}
But when I do this, for some reason it interferes with Twoduser and I am not able to view the single user.
Furthermore, here's a sample JSON response I will get through the url:
{"teams":[{"team_name":"Toronto Maple Leafs"},{"team_name":"Vancouver Canuck"}]}
Moreover, I am fetching all users using Ajax like this:
App.Twod.reopenClass({
findAll : function() {
return new Ember.RSVP.Promise(function(resolve, reject) {
$.getJSON("http://pioneerdev.us/users/index", function(data) {
var result = data.users.map(function(row) {
return App.Twod.create(row);
});
resolve(result);
}).fail(reject);
});
},
findBy : function(user_id) {
return new Ember.RSVP.Promise(function(resolve, reject) {
var user = App.Twod.create();
$.getJSON("http://pioneerdev.us/users/byId/" + user_id, function(data) {
var result = user.setProperties(data.user);
resolve(result);
}).fail(reject);
});
}
});
Though there's one thing, I have a separate Teams route:
this.resource('teamview', function(){
this.resource('teamviewdetail', {
path : ':team_id'
});
});
Which shows all the teams and a single team when you click on a single team.
Can I use that TeamviewController? or Can I fetch team names from Twoduser Controller and push names to the array as I mentioned before?
More Information:
If I use the way I mentioned, I get this error:
Uncaught TypeError: Object [object Object] has no method 'addArrayObserver'
Here's a working jsfiddle on the issue I am experiencing. You can select "Storyboard" from the Designation & then select the user. That will reproduce the issue.
One more Update: Seems using ObjectController instead of ArrayController issue solves the addArrayObserver issue. But still I can't get the teams in the Ember.Select.
The biggest issue here is that you use Array#push instead of pushObject. Ember needs the special methods in order to be aware of changes. Otherwise, it will continue to think that the array of teams is as empty as when you first returned it. Second biggest issue is that your ajax success call isn't accessing the returned data properly.
Also, optionValuePath and optionLabelPath are relative to the individual select option view, so they should start with content, which is the individual item as set on the view. So: content.team_name
So, this is a bit strange. I'm using Backbone and Backbone.localStorage to save remote data to local storage for caching. Pretty standard stuff.
When I run a localStorage.clear() on the entire store, all values are cleared out permanently except for the key that has a string array of values. It gets cleared out on first inspection, but then when storage saves again with Backbone.LocalStorage.sync('create', model); the previous values are back in there.
Of course, if I manually delete the key within Chrome Developer Tools, then it stays gone and isn't repopulated. It's as if the localStorage.clear() call still caches keys with a string array. I've confirmed it is initially cleared out on app start.
I'll post some code and screenshots here on edit, but really it's pretty standard except for the fact those values remain after the key is repopulated. Any ideas here?
EDIT: Lots of fun code to look at:
Collection:
app.DiagnosisCollection = Backbone.Collection.extend({
model: DiagnosisModel,
localStorage: new Backbone.LocalStorage('diagnosis'),
save: function(model) {
Backbone.LocalStorage.sync('create', model);
}
});
Model:
app.DiagnosisModel = Backbone.Model.extend({
urlRoot: app.ApiRoot,
url: app.DiagnosisApi,
initialize: function(options) {
if(!options.query) {
throw 'query must be passed to diagnosisModel';
}
this.local = false;
this._query = options.query;
},
sync: function(method, model, options) {
var diagnosisKey = localStorage.getItem('diagnosis');
var index, diagnosisStore;
if(diagnosisKey) {
diagnosisKey = diagnosisKey.split(',');
}
index = _.indexOf(diagnosisKey, this._query);
if(index !== -1) {
diagnosisStore = localStorage.getItem('diagnosis' + '-' + diagnosisKey[index]);
}
if(diagnosisStore) {
this.local = true;
options.success(JSON.parse(diagnosisStore));
}
else {
this.local = false;
var fetchUrl = this.urlRoot + this.url + this._query;
$.getJSON(fetchUrl, function(data, textStatus) {
if(textStatus !== 'success') {
options.error();
}
options.success(data);
});
}
}
});
return app.DiagnosisModel;
Controller function that does the work:
var self = this;
// Create a new collection if we don't already have one
// The save function puts it in local storage
if(!this.diagnosisCollection) {
this.diagnosisCollection = new DiagnosisCollection();
this.diagnosisCollection.on('add', function(diagModel) {
this.save(diagModel);
});
}
var diagModel = new DiagnosisModel({
query: diagId
});
diagModel.fetch({
success: function() {
var diagView = new DiagnosisView({
model: diagModel
});
if(!diagModel.local) {
self.diagnosisCollection.add(diagModel);
}
self._switchPage(diagView);
},
error: function() {
console.error("Diag Model Fetch Failed");
Backbone.history.navigate('503', { trigger: true });
}
});
By the way, localStorage.clear() call is in app start. It does an API call to see if the version on the server has changed. If the version has changed, then we nuke localStorage.
Instead of localStorage.clear(), use :
localStorage.removeItem('userInfo');
I have faced the same issue in my backbone application and i used this way to clear out the values.
P.s : Yes you need to specify all of your localstorage variables one by one..:(.. But this works well.