Restangular flattening models on post? - angularjs

Say I have the following form comprising a model and a nested model:
<label>Company Name</label>
<input type="text" ng-model="company.name" />
<label>Owner Name</label>
<input type="text" ng-model="company.owner.name" />
Which I post like this:
Restangular.all('companies').post($scope.company);
What I'm expecting on the server end (in this case Rails) is a nested hash something like this:
company:
name: Test Company
owner:
name: Test Owner
But what I'm getting is this:
name: Test Company
company:
name: Test Company
owner:
name: Test Owner
It appears that the models are being flattened, and also the fields from the first model are repeated outside of the scoping.
How can I post the model whilst maintaining its nesting and preferably not repeating the models fields outside of its scope in the hash?

I'm the creator of Restangular.
Could you console.log the output of $scope.company?
Restangular isn't flattering anything. It's just sending the exact JSon that you've provided as a parameter, that's why you should check what is the output of $scope.company.
After that, we can check it further.
Also, have you checked the network tab for the Payload of the request? Is it OK?

I feel the need to clarify this for anyone else finding this question.
Passing $scope.company passes the JS object that is company that does not include the name of the scope variable itself:
{
name: 'Test Company',
owner: {
name: 'Test Owner'
}
}
The server will see this as a POST variable named name that is a string with value 'Test Company' and another variable named owner that is an array with an index named name with value of 'Test Owner`
In PHP it would be this:
$_POST['name']; // would = 'Test Company'
$_POST['owner']; // would = array('name'=>'Test Owner')
If you want it to be an array of properties on the server side referred to as company then you need to encapsulate $scope.company in a JS object itself with a property titled "company":
$scope.company = {
company: {
name : 'Test Company',
owner : {
name : 'Test Owner'
}
}
};
Now on the server side you'll find this:
$_POST['company']; // would = array('name'=>'Test Company','owner'=>array('name'=>'Test Owner'))

Related

Can you explain how the angular filter process works with this code?

I don't understand why the .name is happening in the model instead of the filter. I don't get what is happening behind the scenes when I create nameText.name and bind it to my data. How is my filter actually working?
<input type="text" data-ng-model="nameText.name" />
<input type="text" data-ng-model="nameText.city" />
<li data-ng-repeat="customer in customers | filter:nameText>
<script>
function FilteringController($scope) {
$scope.customers = [
{ name: 'Dave Jones', city: 'Phoenix' },
{ name: 'Jamie Riley', city: 'Phoenix' },
{ name: 'Heedy Wahlin', city: 'Chandler' },
{ name: 'Thomas Winter', city: 'Seattle' }
];
</script>
nameText is an object with the properties name and city in this case. These properties are filled by the input fields. You're filtering your list using an object. What Angular does in this case, is filter the objects in the list using the properties with the matching names. So, for example, if nameText.city is Phoenix, the items in the list are filtered so that only the items with Phoenix for the city property are left. This works the same for name and both can be combined.
Update to answer question in comments: For the exact implementation, I'd suggest looking through the Angular source code. The specific case for a filter using an object is here. What this roughly does is get all the properties of the object passed in as the filter (nameText in your case). It then goes through the list of objects to filter and select all the objects that have properties with values that match the properties that you are searching for. In the source code I referred, you can also see how the other types of search filters you may pass would be handled.

mongoose query: find an object by id in an array

How could I find an image by id in this Schema. I have the id of the User and the id of the image I am looking for. What would be the best way to do this and do all images in this case have different ids or could they have the same id because they don't belong to the same User?
My Schema looks like this:
var userSchema = new Schema({
local: {
email: String,
password: String
},
facebook: {
id: String,
token: String,
email: String,
name: String
},
name: String,
about: String,
images: [{
id: Schema.ObjectId,
link: String,
main: Boolean
}]
});
When you are interested in the full object it is a simple find:
.find({"facebook.id":"<id>", "images.id":<image-id>})
I don't think that there is a way to reduce the image array in the result.
To update a single element in the image array you can use this:
.update({"facebook.id":"<id>", "images.id":<image-id>}, {$set : {"images.$.main" :false} } );
userSchema .find({facebook.id: "some ID",{ "images.id": { $in: [ id1, id2, ...idn] }}
since images are inside the document you can have same ID's however every time you query you should keep in mind that you send some other parameters such as facebook.id or facebook.email along with image id's to retrieve them. Otherwise you end up getting all that might be irrelevant only because you decide to keep same ID's for images.
tl;dr
I struggled with this and came up with a solution. Like you, I was trying to query for a deeply nested object by the _id, but I kept coming up empty with the results. It wasn't until I did some type checking that I realized the id value I was getting from my frontend, while directly supplied by mongoose, was in fact a String and not an Object.
I realize this question was already partially answered before, but that person's solution didn't work for me, and the comment on the answer tells me you wanted to update the specific image you queried for, which is exactly what I was trying to do.
The solution
In order to select an object from the nested array by the _id value, first you'll have to install the npm package bson-objectid and use the provided method to convert your string into an objectId in your query.
In your terminal:
npm i bson-objectid
In your code:
const ObjectId = require('bson-objectid')
userSchema.findOneAndUpdate(
{ "facebook.id": <user-id>, "images._id": ObjectId(<image-id>) },
{ "$set": { "images.$.main": false } },
{ new: true }, // an extra options parameter that returns the mutated document
(err, user) => {
if (err) {
handleErr(err)
} else {
console.log(user)
// do something with new user info
}
)

How to fetch particular objects by their attribute from Array?

I need to get objects with special attribute "type" out of Array. These objects I am going to assign to scope. How can I do this?
The following approach didn't work out for me.
$scope.vendors = {}
$scope.clients = {}
$scope.loadCounterparties = function() {
Counterpartie.query(function(response) {
$scope.vendors = response.type.Vendor;
$scope.clients = response.type.Client
});
};
Response objects look like this
Thanks in advance!
Angular doesn't have something dedicated for this. You need to filter the arrays via plain java script. However you can try using a 3rd party library by the name underscore.js.
It adds many usefull functions like "where":
_.where(list, properties)
Looks through each value in the list, returning an array of all the values that contain all of the key-value pairs listed in properties.
_.where(listOfPlays, {author: "Shakespeare", year: 1611});
=> [{title: "Cymbeline", author: "Shakespeare", year: 1611},
{title: "The Tempest", author: "Shakespeare", year: 1611}]
Here is a link to the library's page
http://underscorejs.org/#where
can use angular forEach but I would use lodash
// assuming one array and two search arguments i.e. client and vendor
var data = response;
$scope.loadCounterparties = _.filter(data, {type: 'Vendor', type: 'Client'});

AngularJS ng-repeat with different objects

I'm new to AngularJS and have written an app which calls an specific API. It gets an object back like this:
posts = [
posts = [...]
users = [...]
]
That's the HTML template:
<div id="{{'post-' + $index}}" ng-repeat="post in posts.posts">
<article>
<h1>{{post.user.username}}</h1>
<span>{{post.date | date: "dd.MM.yy, HH:mm"}}</span>
<p>{{post.content}}</p>
</article>
</div>
I want to show different posts (from the posts object) including the username (which is in the users object). How can I tell Angular that the posts are in the posts object and the proper usernames are in the users object?
ngRepeat creates a new scope for each entry. That scope will contain $index for the current offset in the array. Since it's a new scope the variable posts is now in the $parent scope.
Assuming posts and users contain parallel data and are arrays. You can lookup data in users using $index.
<h1>{{$parent.posts.users[$index].username}}</h1>`
Without seeing the structure of the posts and users object, it's hard to say exactly, but the general idea is to use the users object, with the user_id from the post object, so either something like this:
<h1>{{users[post.user.id].username}}</h1>
Or like this:
<h1>{{getUserNameForPost(post)}}</h1>
$scope.getUserNameForPost = function(post) {
//find the user based of the post however you want
user = $scope.users.filter(function(u) { return u.id = post.user });
return user[0].username;
}
If possible, I would suggest you change the structure of the returned JSON object, to something similar to the one below:
posts = [
{
id : "post id",
title: "Post title",
content: "Post content",
date : "post date",
user : {
id: "user id",
name: "user name"
}
},
....
....
]
This should work.

backbone model saving, validation fails

no amount of Googling is managing to solve my confusion so I thought I'd ask the question on here.
I'm trying to save a model and make use of success/error callbacks. On the backbone documentation it states you save your model like so: model.save([attributes], [options]).
I cannot find anywhere on the documentation that tells you how to save the entire model (i.e. without specifying the attributes), but have come across this question where the second answer says to save the entire model you can do model.save({}, [options]).
However I am trying this to no avail. My code is below:
Backbone Model:
class Student extends Backbone.Model
url: ->
'/students' + (if #isNew() then '' else '/' + #id)
validation:
first_name:
required: true
last_name:
required: true
email:
required: true
pattern: 'email'
schema: ->
first_name:
type: "Text"
title: "First Name"
last_name:
type: "Text"
title: "Last Name"
email:
type: "Text"
title: "Email"
In my view I have the following function:
class Students extends CPP.Views.Base
...
saveModel = ->
console.log "model before", #model.validate()
console.log "model attrs", #model.attributes
#model.save {},
wait: true
success: (model, response) ->
notify "success", "Updated Profile"
error: (model, response) =>
console.log "model after", #model.validate()
console.log "model after is valid", #model.isValid()
console.log "response", response
notify "error", "Couldn't Update"
In the first console.log before the save I am told that the model is valid, via the means of an undefined response. If indeed I look at the model I can see that all three fields are filled in.
Similarly in the next two console logs in the error #model.validate() and #model.isValid() both return undefined and true respectively.
However the response I get from trying to save the model is Object {first_name: "First name is required", last_name: "Last name is required", email: "Email is required"}
Finally in the console.log of the models attributes I get:
Object
created_at: "2012-12-29 23:14:54"
email: "email#email.com"
first_name: "John"
id: 2
last_name: "Doe"
type: "Student"
updated_at: "2012-12-30 09:25:01"
__proto__: Object
This leads me to believe that when I passed {} to my model it was actually trying to save the attributes as nil, else why else would it have errored?
Could someone kindly point out what I'm doing wrong? I'd rather not have to pass each attribute individually to the save!
Thanks in advance
From the suggested answer by Hui Zheng I modified my controller in my server to return the student in JSON format.
However to find the real source of the problem I read the backbone documentation on saving, and found out that when wait: true is given as an option it performs the following:
if (!done && options.wait) {
this.clear(silentOptions);
this.set(current, silentOptions);
}
On further investigation into clear I found
clear: function(options) {
var attrs = {};
for (var key in this.attributes) attrs[key] = void 0;
return this.set(attrs, _.extend({}, options, {unset: true}));
},
From this it looks as if every attribute is being cleared to then be reset. However on clearing my model the validations I wrote will fail (since first_name, last_name, email are required).
On the backbone.validation documentation we are told that we can use the parameter forceUpdate: true, so I have opted to use this when saving my model. I'm going to assume for now (although this might not be good practice) that the data from the server is correct since this has been validated too.
Therefore my final code is:
saveModel = ->
#model.save {},
wait: true
forceUpdate: true
success: (model, response) ->
notify "success", "Updated Profile"
error: (model, response) ->
notify "error", "Couldn't Update"
Are you sure that the model's attributes have been set correctly before save? Even none of its attributes has been set, it may still pass validate(depending on how validate function is defined). Please try to print the model in the console to verify that. BTW, it's better to pass null instead of {} in save, this way the model's set method won't be invoked.
Updated:
According to the Backbone's source code, if passing null as the first argument of save, the model's attributes will be kept intact until the model has been saved on the server successfully. So the other possibility is that your server has succeeded in saving the model but returned a corrupted object, resulting in failure in model's set method. If you still cannot fix the problem, tracing the model.set method might help。

Resources