Loopback's ReferenceMany-like relation with additional fields - mongodb

I need help with loopback framework.
I have two models: Post and Media.
Examples:
Media
{
id: ObjectId("...a1"),
type: "gif",
path: "some/folder"
},
{
id: ObjectId("...a2"),
type: "mp4",
path: "some/folder"
},
Post
{
id: ObjectId("...b1"),
title: "Apollo 13",
content: [
{
mediaId: ObjectId("...a1"),
header: "header-1",
description: "descr-1"
},
{
mediaId: ObjectId("...a2"),
header: "header-2",
description: "descr-2"
}
]
},
{
id: ObjectId("...b2"),
title: "2 seconds to Moon",
content: [
{
mediaId: ObjectId("...a1"),
header: "header-3",
description: "descr-3"
},
]
}
As you can guess I'm going to use MongoDb. I want to describe a relation between this two models, but not sure how to do it in the right way.
If I had only array of mediaIds, I'd make it through referenceMany. Now it's look more like embedsMany, but embeds many what?
I even tried to make something like MediaItem model and give it transient datasource. But I didn't make it works right with rest APIs.
At final I want to get one or many posts with including media data such as type and path fields.
Any thoughts?

Probably you should use HasManyThrough relation (http://loopback.io/doc/en/lb2/HasManyThrough-relations.html) and then include filter (http://loopback.io/doc/en/lb2/Include-filter.html)

Related

Mongoose schema, nested objects with default values

Recently I have started building my own RESTful API using the MEAN stack. So far, so good. Yesterday I bumped into a small issue, which I have been trying to solve, but no successful results have been seen until now...
I have a Mongoose Schema which looks like this:
const hotelSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
stars: {
type: Number,
min: 0,
max: 5,
"default": 0
},
services: [String],
description: {
type: String,
"default": "No description"
},
photos: [String],
currency: {
type: String,
"default": "Not specified"
},
reviews: [reviewSchema],
rooms: [roomSchema],
location: {
address: {
type: String,
"default": "Not specified"
},
// Always store coordinates longitude E/W, latitude N/S order
coordinates: {
type: [Number],
index: '2dsphere'
}
}
});
And this is how I create the model instance:
Hotel
.create({
name: req.body.name,
description: req.body.description,
stars: parseInt(req.body.stars, 10),
services: _splitArray(req.body.services),
photos: _splitArray(req.body.photos),
currency: req.body.currency,
location: {
address: req.body.address,
coordinates:[
parseFloat(req.body.lng),
parseFloat(req.body.lat)
]
}
Everything works perfect and as expected with a small detail. I am using Advanced Rest Client to make the POST request. It can be seen below:
Request with Rest Client
And this is what I get as a response:
Response from Rest Client
So the problem is that, if I do not enter an address, I would like to see the default value as it can be seen in the schema - "Not specified". Unfortunately, I cannot achieve this.
Can you please help me?
Solved!
Actually it seems that it did work correctly. When I make a GET request to fetch the newly created hotel by id, it actually returns it as I would like to, with "address" set to "Not specified".
The fact that after creating the hotel with Model.create() function, the callback that is returned contains the hotel, but without "address" being "Not specified".
I tried the request with Postman as well, the result is the same - no address set.
But anyway, I see that it actually works. I will try to find why the returned object in the callback is not complete though....

About normalization in Redux

Assume you're making Reddit where each Subreddit has many Post and each Post has many Comment. Then the API response probably looks like this:
subreddits: [{
title: "food"
posts: [{
id: "",
body: "..",
comments: [{
id: ".."
body: "..",
}]
..morePosts
},
title: "culture"
posts: [{
id: "",
body: "..",
comments: [{
id: ".."
body: "..",
}]
..morePosts
},
]
But since Redux discourages such nested state, we normalize the data structure before we feed them into reducers. Then, the data can be represented like this:
subredditByTitle: {
food: {
id: subreddit_1,
title: "food"
posts: [post_1, post_2]
}
culture: {
id: subreddit_2,
title: "culture"
posts: [post_3, post_4]
}
}
postsById: {
post_1: {
body: ".."
comments: [comment_1, comment_2]
},
post_2: {
body: "..",
comments: [comment_3, comment_4]
}
}
commentsById: {
comment_1: {
body: ".."
},
comment_2: {
body: ".."
}
}
But it feels a bit awkward to normalize the backend data like this when I use MongoDB, especially when I am using subdocuments. In a relational DB, it makes sense to have a lookup table (e.g. PostsById) for every DB table, does it make sense to do the same for every DB collection? My gut feeling is that instead of trying to normalize everything, it might be better to have one reducer for each document, but I am not sure what the best practice might be.
You should really normalize everything and have entities object in your store where you put all your entities. I tried many approaches but IMHO this is the only true way.
I am using it for things which would be unthinkable to do without this approach but they are out of scope of this answer other more common ones are pagination and asking for data only when you need them application feels super snappy when there is no unnecessary data loading.
I highly recommend to take a really good look at this tiny piece of code and the redux real world example as whole there is really much to learn from that. Your entities reducer would look different obviously but you should be able to write your own to suit your needs

What exactly is "data" that is passed to responses?

I'm writing a custom response that takes data as an input, and I am finding strange properties being added, namely:
add: [Function: add],
remove: [Function: remove]
When I log out some example data, I get:
[ { books:
[ { id: 1,
title: 'A Game of Thrones',
createdAt: '2015-08-04T04:53:38.043Z',
updatedAt: '2015-08-04T04:53:38.080Z',
author: 1 } ],
id: 1,
name: 'George R. R. Martin',
createdAt: '2015-08-04T04:53:38.040Z',
updatedAt: '2015-08-04T04:53:38.073Z' },
{ books:
[ { id: 2,
title: 'Ender\'s Game',
createdAt: '2015-08-04T04:53:38.043Z',
updatedAt: '2015-08-04T04:53:38.080Z',
author: 2 },
{ id: 3,
title: 'Speaker for the Dead',
createdAt: '2015-08-04T04:53:38.043Z',
updatedAt: '2015-08-04T04:53:38.081Z',
author: 2 } ],
id: 2,
name: 'Orson Scott Card',
createdAt: '2015-08-04T04:53:38.042Z',
updatedAt: '2015-08-04T04:53:38.074Z' } ]
Which looks innocent enough, but results in the strange add and remove functions when I use a custom serializer on it. If I take this data and hard-code it straight into the serializer, those are not present. Apparently something is lurking inside of data that's not being printed to the console.
So, what is data?
Edit: So, I'm still not quite sure what other magical properties live in here, but:
Object.keys(data[0].books))
reveals
[ '0', 'add', 'remove' ]
Which is where those are coming from. Why is this included in the data passed to custom responses? And what else might be hiding in there...
More importantly, how do I strip this gunk out and make data a normal object?
JSON.parse(JSON.stringify(data));
That cleans it up nicely, though it feels like a hack. (Actually, it's definitely a hack.)
I assume your data attribute is returned by a database query. e.g.:
Model.find(...).exec(function (err, data) { ... });
But what are these .add() and .remove() methods?
Here is what you can find in the docs:
For the most part, records are just plain old JavaScript objects (aka POJOs). However they do have a few protected (non-enumerable) methods for formatting their wrapped data, as well as a special method (.save()) for persisting programmatic changes to the database.
We can go deeper:
"collection" associations, on the other hand, do have a couple of special (non-enumerable) methods for associating and disassociating linked records. However, .save() must still be called on the original record in order for changes to be persisted to the database.
orders[1].buyers.add({ name: 'Jon Snow' });
orders[1].save(function (err) { ... });
So these methods (.add(), .remove(), .save()) are useful if you play with "collection" associations.
How to remove them?
You'll need to use .toObject() which returns a cloned model instance stripped of all instance methods.
You might want to use .toJSON() that also returns a cloned model instance. This one however includes all instance methods.

Property named "type" on embedded records with RESTSerializer in Ember Data - Error no model was found

I use Ember Data 1.13.4.
I have a model with some embedded records, let's say they look like this:
var Partner = DS.Model.extend({
name: DS.attr(),
addresses: DS.hasMany('address', { async: false } ),
});
var Address = DS.Model.extend({
type: DS.attr(),
zip: DS.attr(),
city: DS.attr(),
street: DS.attr()
});
The API sends back the Address records embedded in the Partner records. Example response:
{
"partners": [
{
"id": 47,
"name": "Johnny",
"addresses": [
{
"id": 7,
"type": "MAIN",
"zip": "1234",
"city": "City-X",
"street": "6. Nowhere"
}
],
},
]
}
The problem is that type on the Address model is just a normal property, but Ember Data wants it to be the type of the embedded model, and I get this assertion message:
Error: No model was found for 'MAIN'
Where 'MAIN' is the content of the type property.
I can't change how the API sends back data, I need to adapt to it.
How do I do this?
Edit: Important detail which I forgot to include the first time.
The API has a /partners/search endpoint, which I access with a direct ajax request, then I (supposedly) need to call this.store.pushMany('partner', this.store.normalize('partner', response.partners)); And this is when the No model was found for 'MAIN' is raised.
Your { partners: { addresses: [ ... ] } } is returning an embedded data, but because you are providing an object with id and type ember-data is understanding that the api returns an polymorphic association. And trying to find a model called MAIN, but since it does't exist Error: No model was found for 'MAIN' is raised.
In order to tell ember-data that your relationship is embedded you need to override the PartnerSerializer and include the DS.EmbeddedRecordsMixin.
App.ApplicationAdapter= DS.RESTAdapter;
App.ApplicationSerializer = DS.RESTSerializer;
App.PartnerSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
addresses: { embedded: 'always' }
}
})
A live demo of this sample running http://emberjs.jsbin.com/nufofehota/1/edit?html,js,output

In WCF Data Services, can I enclose a list of references to another entity while creating a new object?

I have a table Article, a table Tag and a joint table to associate a tag to an article.
While creating a new Article, by sending a POST request to /Service.svc/Articles, is it possible to enclose in the JSON object a list of Tag ids to be associated?
Something like:
{
title: "My article title",
text: "The content:",
Tags: [ { id: 1 }, { id: 2 }, { id: 3 } ]
}
If not can I send the list of tags in one request? For example:
/Service.svc/Articles(1)/Tags
[ { id: 1 }, { id: 2 }, { id: 3 } ]
Or do I have to make as many requests as they are tags?
Thank you very much in advance.
You can modify just the links by POST/PUT/DELETE to the $links URL as described here: http://www.odata.org/developers/protocols/operations#CreatingLinksbetweenEntries
The samples there use ATOM/XML, but the respective JSON format is also possible.
To send multiple operations to the server in one request (to save the roundtrips) you can create a batch request as described here:
http://www.odata.org/developers/protocols/batch