We are developing a new app which will be served online in saas model. User will get access to certain tools and some tutorials on how to use it.
My question is what will be the best approach to make both (interface and content) multilingual. To give you an example - imagine simple navigation with following links:
-section 1
|-page a
|-page b
-section 2
-section 3
|-page c
Each page includes obviously certain labels, titles, buttons etc.
I've been searching for a while and the closest answer I found was here:
Schema for a multilanguage database
however, it describes approach regarding a relational database.
After studying other questions it look like the best approach would be to store each name of section/page/label/title/button as an object with ID keep translation in a separate table including the corresponding ID of the object, language and content. Within SQL world simple join would do the work, however since we are using Mongo I guess there is the better way to do that so.
I would propose to store all translations in your mongoDb database, and then, using an Express API, perform request from the front page about the translations.
[FRONT] ----> [Send mongoDB _id(s)] ---> [EXPRESS API] ===> [MONGOOSE]
[FRONT] <---------- [Translations] <---- [EXPRESS API] <=== [MONGOOSE]
Mongoose Schema
const Content = Schema({
// Type of item you are dealing with
contentType: {
type: String,
enum: ['link', 'title', 'section', 'page'],
},
// Value of translations
translations: [{
languageName: String,
value: String,
}],
// All Contents that are inside
childs: [{
type: Schema.Types.ObjectId,
ref: 'Content',
}],
});
Example of data (MongoDB Document)
[{
_id: '00000000000000000000001',
contentType: 'section',
translations: [{
languageName: 'French',
value: 'Section Enfant',
}, {
languageName: 'English',
value: 'Child Section',
}],
dads: ['00000000000000000000002'],
}, {
_id: '00000000000000000000002',
contentType: 'page',
translations: [{
languageName: 'French',
value: 'Page Baguette',
}, {
languageName: 'English',
value: 'Tee Page',
}],
childs: [],
}]
Example of Request: Retrieve all data about one random section
Content.find({
contentType: 'section',
})
.then((ret) => {
if (!ret) return [];
// Here recursively get childs and childs and childs
// until you got everything
})
.then(() => {})
.catch(err => {});
Tools
Mongoose
----> Mongoose Queries
----> Mongoose Populate
Express
PS: Here is a github project that explains how to start a web/node project from scratch (tools to install ...etc) [Babel, Gulp, Webpack, ES6...]
Related
Hello I am new to nodejs and mongodb.
I have 3 models:
"user" with fields "name phone"
"Shop" with fields "name, address"
"Member" with fields "shop user status". (shop and user hold the "id" of respective collections).
Now when I create "shops" api to fetch all shop, then I need to add extra field "isShopJoined" which is not part of the model. This extra field will true if user who see that shop is joined it otherwise it will be false.
The problem happens when I share my model with frontend developers like Android/iOS and others, They will not aware of that extra field until they see the API response.
So is it ok if I add extra field in shops listing which is not part of the model? Or do I need to add that extra field in model?
Important note
All the code below has NOT been tested (yet, I'll do it when I can setup a minimal environment) and should be adapted to your project. Keep in mind that I'm no expert when it comes to aggregation with MongoDB, let alone with Mongoose, the code is only here to grasp the general idea and algorithm.
If I understood correctly, you don't have to do anything since the info is stored in the Member collection. But it forces the front-end to do an extra-request (or many extra-requests) to have both the list of Shops and to check (one by one) if the current logged user is a Member of the shop.
Keep in mind that the front-end in general is driven by the data (and so, the API/back-end), not the contrary. The front-end will have to adapt to what you give it.
If you're happy with what you have, you can just keep it that way and it will work, but that might not be very effective.
Assuming this:
import mongoose from "mongoose";
const MemberSchema = new mongoose.Schema({
shopId: {
type: ObjectId,
ref: 'ShopSchema',
required: true
},
userId: {
type: ObjectId,
ref: 'UserSchema',
required: true
},
status: {
type: String,
required: true
}
});
const ShopSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
address: {
//your address model
}
});
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
phone: {
type: String,
required: true,
},
// Add something like this
shopsJoined: {
type: Array,
default: [],
required: true
}
});
You could tackle this problem via 2 ways:
MongoDB Aggregates
When retrieving (back-end side) the list of shops, if you know the user that made the request, instead of simply returning the list of Shops, you could return an aggregate of Shops and Members resulting in an hybrid document containing both the info of Shops and Models. That way, the front-end have all the info it needs with one back-end request.
Important note
The following code might not work as-is and you'll have to adapt it, I currently have nothing to test it against. Keep in mind I'm not very familiar with aggregates, let alone with Mongoose, but you'll get the general idea by looking the code and comments.
const aggregateShops = async (req, res, next) => {
try {
// $lookup will merge the "Model" and "Shop" documents into one
// $match will return only the results matching the condition
const aggreg = await Model.aggregate({$lookup: {
from: 'members', //the name of the mongodb collection
localField: '_id', //the "Shop" field to match with foreign collection
foreignField: 'shopId', //the "Member" field to match with local collection
as: 'memberInfo' //the field name in which to store the "Member" fields;
}, {
$match: {memberInfo: {userId: myUserId}}
}});
// the result should be an array of object looking like this:
/*{
_id: SHOP_OBJECT_ID,
name: SHOP_NAME,
address: SHOP_ADDRESS,
memberInfo: {
shopId: SHOP_OBJECT_ID,
userId: USER_OBJECT_ID,
status: STATUS_JOINED_OR_NOT
}
}*/
// send back the aggregated result to front-end
} catch (e) {
return next(e);
}
}
Drop the Members collection and store the info elsewhere
Instinctively, I would've gone this way. The idea is to either store an array field shopsJoined in the User model, or a membersJoined array field in the Shops model. That way, the info is retrieved no matter what, since you still have to retrieve the Shops and you already have your User.
// Your PATCH route should look like this
const patchUser = async (req, res, next) => {
try {
// How you chose to proceed here is up to you
// I tend to facilitate front-end work, so get them to send you (via req.body) the shopId to join OR "un-join"
// They should already know what shops are joined or not as they have the User
// For example, req.body.shopId = "+ID" if it's a join, or req.body.shopId = "-ID" if it's an un-join
if (req.body.shopId.startsWith("+")) {
await User.findOneAndUpdate(
{ _id: my_user_id },
{ $push: { shopsJoined: req.body.shopId } }
);
} else if (req.body.shopId.startsWith("-")) {
await User.findOneAndUpdate(
{ _id: my_user_id },
{ $pull: { shopsJoined: req.body.shopId } }
);
} else {
// not formatted correctly, return error
}
// return OK here depending on the framework you use
} catch (e) {
return next(e);
}
};
Of course, the above code is for the User model, but you can do the same thing for the Shop model.
Useful links:
MongoDB aggregation pipelines
Mongoose aggregates
MongoDB $push operator
MongoDB $pull operator
Yes you have to add the field to the model because adding it to the response will be only be a temporary display of the key but what if you need that in the future or in some list filters, so its good to add it to the model.
If you are thinking that front-end will have to be informed so just go it, and also you can set some default values to the "isShopJoined" key let it be flase for the time.
I've run into a strange issue. I've started to use MongoDB and it's most likely me doing something terrible wrong.
I have the following models setup:
var cartSchema = mongoose.Schema({
owner: { type: Schema.Types.ObjectId, ref: 'users' },
productline: [{ type: Schema.Types.ObjectId, ref: 'Productline' }]
});
var productlineSchema = mongoose.Schema({
cart: { type: Schema.Types.ObjectId, ref: 'Cart' },
product: { type: Schema.Types.ObjectId, ref: 'products' },
quantity: Number
});
Initially the Cart is setup with an empty array when the User registers, but then we add Productline objects to it later (which works since I can see data in there in my GUI.).
I'm trying to read the name value which is intended to be reached from cart -> productline -> product
for (var i=0; i < cartModel.productline.length; i++) {
console.log(cartModel.productline[i].product.name);
}
But getting TypeError: Cannot read property 'name' of undefined on exactly that line, which means that product == "undefined".
However, when I'm checking in my MongoDB with MongoDB Compass, then I can see that there is infact a connection between them, and the id's looks accurate as far as I can tell, so it should be able to read it.
So either I'm trying to reach the values in the wrong way, cartModel.productline[0].product.name.
Or my code doesn't realize that the object has been updated, which is strange since I even made sure to use Cart.findOne(query, function(err, cartModel) { ... } to be sure I get a fresh one from the database.
Anyone has any ideas? I'll be glad to post some more code if needed, I just tried to find the most relevant parts above, but I might be missing something somewhere else...
I actually managed to solve this by myself. For people who are having problems with nested objects, I recommend you looking into the mongoose-deep-populate plugin: https://github.com/buunguyen/mongoose-deep-populate
It helped me out a lot, and my final query ended up like this:
Cart.findOne({_id: cart}).deepPopulate('productline.product').exec(function (err, docs) {
// docs is JSON data which can be used to reach the nested data.
});
I have also encountered similar error recently
Demo
When I use: .populate('productline')
Cart.findOne({_id: cart}).populate('productline').exec(function(err, docs) {
/* docs.productline items represent the correct model but their
fields are undefined except the `_id` */
})
Solution
When I use: .populate({path: 'productline'})
Cart.findOne({_id: cart}).populate({path: 'productline'}).exec(function(err, docs) {
/* docs.productline items represent the correct model with all properties
fetched with the correct values */
})
Other solution
This example from http://mongoosejs.com/docs/populate.html helped me
Story
.find(...)
.populate({
path: 'fans',
match: { age: { $gte: 21 }},
select: 'name -_id', /* populates only these fields in the `fans` collection */
options: { limit: 5 }
})
.exec()
I have an self referencing employee schema in mongoose.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Employee = new Schema({
name: String,
description: {
type: String,
default: 'No description'
},
manager: {
type: Schema.Types.ObjectId,
ref: 'Employee',
default: null
},
reportee: [{
type: Schema.Types.ObjectId,
ref: 'Employee'
}]
});
An employee can a manager & can have several reportee. If manager is null then the employee is treated as top level employee.
I need to created hierarchy based on this model. I am struggling to generate desired output.
So far I have tried to use popluate() & mongoose-deep-populate module but I am unable to get the desired output. I wonder its becuase I have a self referencing model. Or may be I am not using these two options properly.
This is what I have tried with deep-populate module. It seems to be populating reportee model but not repotree of reportee model. In short its only populating 1 level of records.
Employee.deepPopulate(employees, 'reportee.reportee.reportee.reportee.reportee', function (err, _employee) {
employees.forEach(function (employee) {
});
});
Please suggest how can I retrive all the employee hierarchy ?
To answer my own question, I am using mongoose-deep-populate library.
To use it we need to install the module:
npm install mongoose-deep-populate
//Register the plugin
var deepPopulate = require('mongoose-deep-populate');
Employee.plugin(deepPopulate);
And then use this code:
Employee.deepPopulate(employees, 'reportee.reportee.reportee.reportee.reportee', function (err, _employee) {
employees.forEach(function (employee) {
});
});
This will load 5 levels of reportees as we have mentioned reportee.reportee.reportee.reportee.reportee
I have the following mongoose schema used in my MEAN app:
// schema
var categorySchema = new Schema ({
sId: String,
name: String,
parentId: String,
merchants: {},
attributes: {}, /* used to generate pivots and then discarded. */
pivots: [],
_id: {type: String, select: false},
/*other elements in db also not returned by using select: false*/
});
here's the problem. I have a mongodb that is not created by my app, rather its actual schema is defined elsewhere. I have access to this data but want it in a completely different format then what is actually in the database. This is working great by using:
categorySchema.options.toJSON = {
transform: function(doc, ret, options) {
however the schema doesn't represent the full API contract because the "attributes" field in the Schema is deleted in the transform. Pivots aren't in the database but are needed in the schema for mongoose to return it. Thankfully I like this, I want the schema to reflect exactly what I am returning, not what is in the database because frankly, it's a mess and I'm heavily transforming it, so I can give it to other engineers and use it for automated testing.
How do I get attributes out of the schema but still able to use in the transform?
turns out mongoose has function transforms. So I can do:
merchants: { type: {}, get: objToArr},
and that function is called.
just be sure to set:
Schema.set('toObject', { getters: true });
Schema.set('toJSON', { getters: true });
to true.
I recently began to develop on sailsJs and not understanding the subtleties
Please explain to me what is populate in SailsJs and who can please do simple example
Thanks in advance ?
whats is ?
User.find({ name: 'foo' })
.populate('pets', { name: 'fluffy' })
.exec(function(err, users) {
if(err) return res.serverError(err);
res.json(users);
});
populate is used for associations. When your model is something like this:
// User.js
module.exports = {
attributes: {
name: {
type: "string"
},
pet: {
model: "pet"
}
}
}
Here pet attribute of user collection is a reference to pet table. In user table it will store only the id column of pet. However, when you do a populate while find, then it will fetch the entire record of the pet entry and display it here. This is just for one to one association. You can have many to one associations as well as many to many. See this documentation for more details
sailsjs project is use the wateline ORM. you can see the document. if you want to use 'populate()', you need define Associations in the model.
.populate()
populate is used with associations to include any related values specified in a model definition. If a collection attribute is defined in a many-to-many, one-to-many or many-to-many-through association the populate option also accepts a full criteria object. This allows you to filter associations and run limit and skip on the results.
as you example, you need do like this:
User.js
// A user may have many pets
var User = Waterline.Collection.extend({
identity: 'user',
connection: 'local-postgresql',
attributes: {
firstName: 'string',
lastName: 'string',
// Add a reference to Pets
pets: {
collection: 'pet',
via: 'owner'
}
}
});
Pet.js
// A pet may only belong to a single user
var Pet = Waterline.Collection.extend({
identity: 'pet',
connection: 'local-postgresql',
attributes: {
breed: 'string',
type: 'string',
name: 'string',
// Add a reference to User
owner: {
model: 'user'
}
}
});
you can ready the doc, and you can use it very easy
To resume what is .populate() (used by waterline) is a little what join is used by SQL.
.populate() allows you to join the tables in your database.
The link identifier is to be defined in your model.
In other words, to associate a user (who is in the "User" table) with a dog (who is in the "Dog" table), you use populate.
To resume your example:
You are looking for the user => User.find ({name: my_user})
You are looking for the dog named "fluffy" => {name: 'fluffy'}
You are looking for the dog 'fluffy' which is associated with your user (belongs) => populate ('pets')
Which give:
User.find({ name: 'foo' })
.populate('pets', { name: 'fluffy' })
.exec(function(err, users) {
if(err) return res.serverError(err);
res.json(users);
}
This association ("pets"), you define it in your models "User" and "Pets" like the example above.
Populate is all about displaying the content of the (id) on which it was refreed
"abc": [{
type: ObjectId,
ref: "xyz"
}],