EmberJS Handling Complex Object returned from REST Api - rest

I have the following user object returned from a REST Api:
{
user: {
id: 3451,
name: "First Last",
favorites: {
designs: {
name: "Design 1",
url: "Url 1"
},
typo: {
name: "Typo 1",
url: "Url 2"
},
games: {
name: "Game 1",
url: "Url 3"
}
}
}
}
I got this response from the url /users/3451.
And here's my User model:
App.User = DS.Model.extend({
name: DS.attr('string'),
favorites: DS.attr(), // ??? What to put here?
});
I have no problem displaying the {{name}}. But in the favorites, I don't know.
I tried using {{#each}}
{{#each favorites}}
{{#key}} - {{name}}
{{/each}}
but no luck.It throws an error: Error: Assertion Failed: The value that #each loops over must be an Array. You passed [object Object]
What is the correct way of handling these kinds of complex objects in EmberJS? Please help.

I think the error is pretty self explanatory: you need to be looping over an array, not an object. Here's how I would convert that object to an array, while saving the key (put this in your model):
favoritesArray: function() {
var favorites = this.get('favorites');
return Em.keys(favorites).map(function(key) {
return {
key: key,
data: favorites[key]
};
});
}.property('favorites.#each.{name,url}')
Then, in your template:
{{#each favoritesArray}}
{{key}} - {{data.name}}
{{/each}}
That would be the easiest way to do it. But if you're looking for a slightly better way (in my opinion), you can user a type transform to convert the data to the format you need at the time of (de)serialization.
EDIT: Just for a bit of background info, I believe the reason that Ember.js doesn't support iterating over objects is because there is no way to bind to the object keys. Ember.js knows to update a bound helper when the dependent key observers are fired, but as far as I know, there is no way to observe the keys of an object. Something like this might be possible using an Map or similar, but I don't think that it's built in functionality.

Related

How can I search on a field and return the object

I made a beer matching dating app for a school assignment. Unfortunately am I having a bit of trouble finding out how to search in my database on a template injected number as object.
I've tried this I read on MongoDB docs
The database looks like this :
beerProfile: Object
↳24589: Object
↳name: "Heineken"
img: "https://untappd.akamaized.net/site/beer_logos/beer94130_52756_sm.jpeg"
description: "Heinken is a beer"
bid:"24589"
So beerProfile is an object and has the object 24589 inside it. Inside the 24589 object are name, imd, description and bid.
I tried to use the find() function. ( the collection is called users )
db.collection('users').find( { [24589]: [{name: [Heineken]}] }, { name: 1, bid: 1 }, done);
And I also tried :
db.collection('users').find( { $text: { $search: 24589 } }, done);
I would like to make it return the object values of the 24589 object. Does anyone how I can achieve this ?
I think your "schema" became unnecessarily complex by using a variable (24589) as a key. You should change it to something like this:
beerProfile: Object
↳beer: Object
↳name: "Heineken"
img: "https://untappd.akamaized.net/site/beer_logos/beer94130_52756_sm.jpeg"
description: "Heinken is a beer"
bid:"24589"
Then you can use a simple find():
db.collection('users').find( { "beer.bid": "24589"})

Using objects as options in Autoform

In my Stacks schema i have a dimensions property defined as such:
dimensions: {
type: [String],
autoform: {
options: function() {
return Dimensions.find().map(function(d) {
return { label: d.name, value: d._id };
});
}
}
}
This works really well, and using Mongol I'm able to see that an attempt to insert data through the form worked well (in this case I chose two dimensions to insert)
However what I really what is data that stores the actual dimension object rather than it's key. Something like this:
[
To try to achieve this I changed type:[String] to type:[DimensionSchema] and value: d._id to value: d. The thinking here that I'm telling the form that I am expecting an object and am now returning the object itself.
However when I run this I get the following error in my console.
Meteor does not currently support objects other than ObjectID as ids
Poking around a little bit and changing type:[DimensionSchema] to type: DimensionSchema I see some new errors in the console (presumably they get buried when the type is an array
So it appears that autoform is trying to take the value I want stored in the database and trying to use that as an id. Any thoughts on the best way to do this?.
For reference here is my DimensionSchema
export const DimensionSchema = new SimpleSchema({
name: {
type: String,
label: "Name"
},
value: {
type: Number,
decimal: true,
label: "Value",
min: 0
},
tol: {
type: Number,
decimal: true,
label: "Tolerance"
},
author: {
type: String,
label: "Author",
autoValue: function() {
return this.userId
},
autoform: {
type: "hidden"
}
},
createdAt: {
type: Date,
label: "Created At",
autoValue: function() {
return new Date()
},
autoform: {
type: "hidden"
}
}
})
According to my experience and aldeed himself in this issue, autoform is not very friendly to fields that are arrays of objects.
I would generally advise against embedding this data in such a way. It makes the data more difficult to maintain in case a dimension document is modified in the future.
alternatives
You can use a package like publish-composite to create a reactive-join in a publication, while only embedding the _ids in the stack documents.
You can use something like the PeerDB package to do the de-normalization for you, which will also update nested documents for you. Take into account that it comes with a learning curve.
Manually code the specific forms that cannot be easily created with AutoForm. This gives you maximum control and sometimes it is easier than all of the tinkering.
if you insist on using AutoForm
While it may be possible to create a custom input type (via AutoForm.addInputType()), I would not recommend it. It would require you to create a template and modify the data in its valueOut method and it would not be very easy to generate edit forms.
Since this is a specific use case, I believe that the best approach is to use a slightly modified schema and handle the data in a Meteor method.
Define a schema with an array of strings:
export const StacksSchemaSubset = new SimpleSchema({
desc: {
type: String
},
...
dimensions: {
type: [String],
autoform: {
options: function() {
return Dimensions.find().map(function(d) {
return { label: d.name, value: d._id };
});
}
}
}
});
Then, render a quickForm, specifying a schema and a method:
<template name="StacksForm">
{{> quickForm
schema=reducedSchema
id="createStack"
type="method"
meteormethod="createStack"
omitFields="createdAt"
}}
</template>
And define the appropriate helper to deliver the schema:
Template.StacksForm.helpers({
reducedSchema() {
return StacksSchemaSubset;
}
});
And on the server, define the method and mutate the data before inserting.
Meteor.methods({
createStack(data) {
// validate data
const dims = Dimensions.find({_id: {$in: data.dimensions}}).fetch(); // specify fields if needed
data.dimensions = dims;
Stacks.insert(data);
}
});
The only thing i can advise at this moment (if the values doesnt support object type), is to convert object into string(i.e. serialized string) and set that as the value for "dimensions" key (instead of object) and save that into DB.
And while getting back from db, just unserialize that value (string) into object again.

Sailsjs - Prevent non-model fileds to be saved in mongo document

I recently started working with Sails and mongo.
I use Sails blueprints to generate part of my api.
The problem is, that the request body I send is being saved to the mongo collection, regardless of the fields defined in the model.
So for example, let's say I have the following Event model:
module.exports = {
attributes: {
title: {
type: 'string',
required: true
},
}
}
When I Send a POST request to the /event/ endpoint with the following params:
{"title":"Some Event", "random":"string"}
The saved mongo document contains also the "random":"string" value, even though it's not part of the model.
I've tried to come up with some common method to remove non-model attributes before creation for all models, but the possible solutions seemed not right and dirty.
Am I missing something?
Any help would be appreciated!
You can use schema option in your model. Just add it to model declaration and that's it.
// api/models/Model.js
module.exports = {
schema: true,
attributes: {
title: {
type: 'string',
required: true
}
}
};

No updating collection in mongodb

I use 0.6.4. Don't work Collection.update(t.data._id, { $set: { name: e.currentTarget.value}}); Session.set("edit-" + t.data._id, false);.
I'd recommend using jQuery to extract the value: $(e.currentTarget).val(). Also, assuming the person template is rendered from an {{#each people}}, you could probably just do this._id, but it's hard to tell without seeing the templates.
People.update(this._id, { $set: { name: $(e.currentTarget).val()}});
I'd also suggest logging these values to the console before the update to make sure the callback is getting executed and that you are reading the right values.

Understanding Relationships & Foreign Keys in Mongoose

In MongoDB/Mongoose, how do I define a relationship? I think there are a few ways I've seen, but I'm not sure I understand the differences or when do I use which. I am using Mongoose 3
I've defined Todo and TodoList model, where the relationship is obvious. So following the docs http://mongoosejs.com/docs/documents.html, I've defined classes like:
# Todo.coffee
mongoose = require "mongoose"
todoSchema = mongoose.Schema
name: String
desc: String
dueOn: Date
completedOn: Date
module.exports = mongoose.model "Todo", todoSchema
# TodoList.coffee
mongoose = require "mongoose"
Todo = require "./Todo"
todoListSchema = mongoose.Schema
name: String
todos: [Todo.schema]
module.exports = mongoose.model "TodoList", todoListSchema
Then I tried testing the classes:
list = new TodoList
name: "List 1"
todos: [
{ name: "Todo 1", desc: "Hello", dueOn: new Date(2012,10,1), completedOn: new Date(2012,10,2) }
{ name: "Todo 2" }
{ name: "Todo 3", desc: "Hello 2", dueOn: new Date(2012,10,6), completedOn: new Date(2012,10,2) }
{ name: "Todo 4" }
]
#list.todos.push { name: "Todo 5" }
console.log "List", list
list.save (err) ->
if !err
TodoList.find {}, (err, lists) ->
console.log "TODOS"
console.log lists.length, lists
done(err)
else
console.log "ERROR!!!"
done err
When I try to do Todo.find() I get nothing, so the Model (Todo.coffee) is kind of redundant? It looks like Todo are stored in TodoList, as a user I may not care, but I wonder what are the implications? Eg. will the document get too large? 1 TodoList with too many Todos? Does that matter? What if I allow nested Todos (not that I want to do itm just for understanding), is it better to store documents separately then? How do I do that, if I so desire, and when do I do it?
I saw another method, in Mongoose 2 actually. I don't know, if it is possible in 3. Something like using ObjectId instead of nested docs. Maybe thats to store it separately?
I'm still new to Node, Mongoose, and Mongo, but I think I can address at least part of your question. :)
Your current method is the same as I tried doing at first. Basically, it ends up storing it very similarly to this (written in JS, since I don't know CoffeeScript):
var todoListSchema = new mongoose.Schema({
name: String,
todos: [{
name: String,
desc: String,
dueOn: Date,
completedOn: Date
}]
});
I later found this method, which is what I was looking for, and I think what you were intending:
var todoListSchema = new mongoose.Schema({
name: String,
todos: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Todo' //Edit: I'd put the schema. Silly me.
}]
});
This stores an array of ObjectIds, which you can then load using Query#populate in Mongoose.
I don't know of the technical implications, but it makes more sense in my brain if I keep them separate, so that's what I'm doing. :)
Edit: Here is a some official docs that might be useful: http://mongoosejs.com/docs/populate.html