Dynamically index nested fields in mongoose - node.js

I would like to be able to index some nested fields in my mongodb using mongoose. The fields in question are the "name" and "description". They are in English and Arabic languages.
They look like this after creation:
"name": {
"en": "Trace Chandelier",
"ar": "نجفة"
},
"description": {
"en": "Steel canopy.",
"ar": "نجفة من الستاينلس ستيل و النحاس. بمكن تغيير الارتفاع."
}
The "en" and "ar" keys are added dynamically from the frontend or postman. More languages will be added in the future.
The schema look like this:
const productSchema = new mongoose.Schema({
name: {
type: Map,
of: String,
required: true
},
description: {
type: Map,
of: String,
required: true
}
}
});
I know in order to create an index in mongoose, it can be like this:
productSchema.index(
{
name: 'text',
description: 'text',
},
{default_language: "none", language_override: "none"}
);
I'd like to index these fields to be able to search for any string in them.
But I can't do name.en or name.ar. As I previously said that more languages will be added in the future.
How can dynamically index those fields?
Thanks in advance.

Related

How do I update a [Schema.Types.Mixed]?

I am using Schema.Types.Mixed to store "profiles" and here's my Schema
const userSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
trim: true,
},
profiles:{
type: Schema.Types.Mixed
}
}
Suppose I have a document with values:
{
name: "XYZ"
profiles:{
"fb" : {
"name": "XYZ",
"pic": "source",
"link": "www.fb.com/xyz"
},
"twitter" : {
"name": "XYZ",
"pic": "source",
"link": "www.twitter.com/xyz"
}
}
}
I want to add another field "youtube". How do I write updateOne query such that it upserts this new field in profiles? And how do I update values of these inner fields? Suppose I want to update only "link" fields of fb, how do I do that?
You can use the aggregation pipeline of Mongo
How do I write updateOne query such that it upserts this new field in
profiles?
db.profiles.aggregate( [
{
$addFields: {
"profiles.youtube": "add the data here"
}
}
] )
And how do I update values of these inner fields?
db.profiles.aggregate( [
{
$addFields: {
"profiles.twitter.name": "add the data here"
}
}
] )
This will update the existing field. This pipeline works well if you want to make changes to the whole collection. For more details check this.
If you want to do that for one document you can do this:
db.profiles.update({"name": "XYZ" },{$set : {"profiles.youtube":"add data here"}});
// to update existing data
db.profiles.update({"name": "XYZ" },{$set : {"profiles.twitter":"add data here"}});

throw new TypeError(`Invalid schema configuration: \`${name}\` is not ` +

I wrote the below schema. But while running it gives me an error -- throw new TypeError(Invalid schema configuration: \${name}` is not ` + --- can someone help me why does this error comes?. Below shown is my schema, can someone figure out if my schema have some mistakes.
$
jsonSchema: {
bsonType: "object",
properties: {
name: {
bsonType: "string",
description: "must be a string"
},
teacherId: {
bsonType: "objectId",
description: "must be a Object ID"
}
}
}
You are using the MongoDB native syntax to define your schema. If you are using mongoose, mongoose has its own syntax. Please refer to mongoose documentation. For your schema it would be:
var studentSchema = new mongoose.Schema({
name: { type: String },
teacherId: { type: mongoose.Schema.Types.ObjectId }
})
Mmm how I resolved this was to use the Types defined within the Schema import. For some reason importing the Types module directly didn't work -- well for Map at least.
import { Document, Schema, model, Query, Model } from "mongoose";
{
...
metadata: { type: Schema.Types.Map, of: String, required: false },
}
"name" is not a valid attribute anymore in mongoose schemas. Replace it with "type" it will work.
Oh man this is related to wrong name of the schema fields in my case:
participants: [
{
typeof: Types.ObjectId,
ref: "User",
},
]
and I got same error as you. I was so struggled and this typeof - type f* VS code I'm using it at the moment cause this computer can't deal with jet brains, :(

How to search through multiple fields with elasticsearch?

How to search through multiple fields with elasticsearch? I've tried many queries but none of them worked out. I want the search to be case insensitive and one field is more important than the other. My query looks like this:
const eQuery = {
query: {
query_string: {
query: `*SOME_CONTENT_HERE*`,
fields: ['title^3', 'description'],
default_operator: 'OR',
},
},
}
esClient.search(
{
index: 'movies',
body: eQuery,
},
function(error, response) {
},
)
Mapping looks like this:
{
mappings: {
my_index_type: {
dynamic_templates: [{ string: { mapping: { type: 'keyword' }, match_mapping_type: 'string' } }],
properties: {
created_at: { type: 'long' },
description: { type: 'keyword' },
title: { type: 'keyword' },
url: { type: 'keyword' },
},
},
_default_: {
dynamic_templates: [{ string: { mapping: { type: 'keyword' }, match_mapping_type: 'string' } }],
},
},
}
The problem is the type: keyword in your mapping for fields description and title. Keyword type fields are not analyzed i.e they store the indexed data exactly like it was sent to elastic. It comes into use when you want to match things like unique IDs etc. Read: https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html
You should read about analyzers for elasticsearch. You can create your custom analyzers very easily which can change the data you send them in different ways, like lowercasing everything before they index or search.
Luckily, there are pre-configured analyzers for basic operations such as lowercasing. If you change the type of your description and title fields to type: text, your query would work.
Read: https://www.elastic.co/guide/en/elasticsearch/reference/current/text.html
Also, i see you have dynamic templates configured for your index. So, if you do not specify the mappings for your index explicitly, all your string fields (like description and title) will be treated as type: keyword.
If you build your index like this:
PUT index_name
{
"mappings": {
index_type: {
"properties": {
"description": {"type": "text"},
"title": {"type": "text"}, ...
}
}
}
}
your problem should be solved. This is because type: text fields are analyzed by the standard analyzer by default which lowercases the input, among other things. Read: https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-analyzer.html

Using only JsonSchema with Mongoose possible?

I have an api using express, mongodb and I use AJV validation to validate the incoming requests.
//JSONSchema
var recordJsonSchema = {
type: "object",
properties: {
name: { type: "string" },
idNumber: { type: "number" },
content: { type: "string" }
},
required: ['name','idNumber']
}
And I'd use this JSON schema to validate incoming requests like so.
app.post('/record', (req,res) => {
let errors = ajv.inspect(req.body, recordJsonSchema)
return errors ? res.send(errors) : res.send(this.handler(req));
})
This works fine and is very fast. I also like JsonSchema since it follows OpenAPI standards.
Unfortunately, in order to read/write to mongo via mongoose I also need to create a MongoSchema for Record. They are very similar but a bit different in how they handle required fields etc.
var recordSchema = new Schema({
name: { type: "string", required: true },
idNumber: { type: "number", required: true },
content: { type: "string" }
})
So for my model of Record I have two schemas now. One for JSONschema and one for handling Mongo read/writes.
I'm looking for a way to cut MongoSchema, any suggestions?
Maybe this, seems like it imports your ajv schema from the entry and place it in the mongoose schema. https://www.npmjs.com/package/mongoose-ajv-plugin
I have faced with same problem. I think in new mongo 4.4 we can load ajv schema directly to mongodb https://docs.mongodb.com/manual/reference/operator/query/jsonSchema/

Is there a way to only return fields defined within a Mongoose schema?

Let's say I have a collection People with the following document inside:
{
"name": "John",
"age": 25,
"gender": "male"
}
And two Schemas for the document, one that should return all the information, and another that should only return a subset of the information:
var Person = mongoose.Schema({ "name": String, "age": Number, "gender": String}, {collection: "People");
var PersonName = mongoose.Schema({ "name": String }, {collection: "People"});
How do I get PersonName to only return name?
Currently, I have a document with a lot of unnecessary information, and would like to only return a subset of the data when using .find(). I have defined a schema and set it's collection manually. Going against the defined Schema, it returns all fields.
no need to define another schema to get specific property from a collection. Just specify which properties you want to return in find() function as projection option.
Schema.find(query,options,callbackFunction);
like:
person.find({},{name:1}, function(err, docs) {//used name:1 to return only name
if(error) {
return res.status(400).send({msg: "error"});
}
return res.status(200).send(docs);
});
I have an idea of using "select: false" on the Reference schema. We still have to list/define all the related fields.
For example,
{
"name": "John",
"age": 25,
"gender": "male"
}
var Person = mongoose.Schema({ "name": String, "age": Number, "gender": String}, {collection: "People");
var PersonRef = mongoose.Schema({ "name": String, "age": { type: Number, select: false }, "gender": { type: String, select: false },} {collection: "People"});
But this method will only applicable (or useful) when the schema is relatively small for maintainability. If the schema grown bigger, then we will need to set select: false in both (or multiple schema) to avoid auto select in RefSchema.
I'm still looking for a better solution to handle this.

Resources