LoopBack4 MongoDB Auto Increment custom ID - mongodb

LoopBack itself is new for me and I see version 4 is way too different from version 3. My requirement is that I need to have a custom auto incremented id in my mongoDB document every time I create a POST to the REST end point similar to a running id in a MySQL database.
I did check this (auto-increment using loopback.js and MongoDB) and (https://gist.github.com/drmikecrowe/5a5568930bad567d4148aad75c94de5a) with a version 3 setup, but i did not find proper document to replicate the same on version 4.
Currently I am using a basic app with the out of the box REST implementations provided from the loopback 4. Below is an example of my model.
export class Test extends Entity {
#property({
type: 'string',
id: true,
})
_id?: string;
#property({
type: 'number',
generated: true,
required: false
})
id: number;
#property({
type: 'string',
required: true,
})
name: string;
#property({
type: 'boolean',
required: true,
})
val: boolean;
constructor(data?: Partial<Test>) {
super(data);
}
}
My mongodb document should look something like this:
{
"_id" : ObjectId("5c373c1168d18c18c4382e00"),
"id" : 1
"name" : "aaaa",
"val" : true
}
{
"_id" : ObjectId("5c3869a55548141c0c27f298"),
"id" : 2
"name" : "bbbbb",
"val" : false
}

You can do something like in this example
#post('/characters', {
responses: {
'200': {
description: 'Character model instance',
content: {'application/json': {schema: {'x-ts-type': Character}}},
},
},
})
async create(#requestBody() character: Character): Promise<Character> {
//add following lines
let characterId = 1;
while(await this.characterRepository.exists(characterId)){
characterId ++;
}
character.id = characterId;
//add above lines
return await this.characterRepository.create(character);
}
you probably already noticed the auto-increment id feature. When you call the post API multiple times (leave id blank), the id increased by 1 every time. This feature is supported by the in-memory database. But we are using MongoDB in this project. If we want to have that feature, we need to do that programmatically.
For more information follow below link
https://strongloop.com/strongblog/building-online-game-with-loopback-4-pt1/
see the section just above the API Explorer heading
or find for 'auto increment id' you will be taken to that paragraph
Hopefully, this helps, write me if there is any other query.
Thanks

I'm also playing with Mongo and it can autogenerate your id for you.
Specifically, when you create your model, using lb4 model, choosing 'Entity' and then you're prompted:
Let's add a property to Participant
Enter an empty property name when done
? Enter the property name: id
? Property type: string
? Is id the ID property? Yes
? Is id generated automatically? Yes
This will generate your model with the property:
#property({
type: 'string',
id: true,
generated: true,
})
id?: string;
Great.. then when creating your CRUD controller:
? What kind of controller would you like to generate? REST Controller with CRUD functions
? What is the name of the model to use with this CRUD repository? Person
? What is the name of your CRUD repository? PersonRepository
? What is the name of ID property? id
? What is the type of your ID? string
? Is the id omitted when creating a new instance? Yes
? What is the base HTTP path name of the CRUD operations? /persons
Now when hitting your endpoint, the create POST doesn't take an ID, but will return one for you.

You can do something like in this example
let last_record = await this.testRepository.findOne({order: ['id DESC']});
if(last_record) invoice.id = last_record.id+1;
This will generate your model with the property:
#property({
type: 'number',
id: true,
default: 1,
generated: false
})
id: number;
Hopefully, this helps, please write me if there is any other code. Thanks

This class inherits from the DefaultCrudRepository class and overrides the create method. The method uses the "Counters" collection to hold the last id of the current data class (this.entityClass.name). The findAndModify method will prevent duplicate id values from being created.
import {DefaultCrudRepository, Entity, DataObject, Options} from '#loopback/repository';
export class MongoAutoIncIdRepository<T extends Entity, ID, Relations extends object = {}> extends DefaultCrudRepository<T, ID, Relations> {
public async create(entity: DataObject<T>, options?: Options): Promise<T> {
if (!this.dataSource.connected) {
await this.dataSource.connect()
}
let mongoConnector = this.dataSource.connector!
let collection = mongoConnector.db.collection('Counters')
let result = await collection.findAndModify(
{
collection: this.entityClass.name
},
[['_id', 'asc']],
{
$inc: {value: 1}
},
{
upsert: true,
new: true
})
console.log(result)
// #ts-ignore
entity.id = result.value.value
return super.create(entity, options)
}
}
It's easy to use. Inherit your repository not from DefaultCrudRepository, but from MongoAutoIncIdRepository if auto increment is required. Then, when the create method is called, the id will increase by 1 automatically.

Related

Mongoose set default on field update if field is not present or null

I have a mongoose schema like this suppose:-
var mSchema = new Schema({
name: { type: String, required: true}
});
and have been using this schema for a year and now i want to add gender to it like this :-
var mSchema = new Schema({
name: { type: String, required: true},
gender: { type: String, default: 'Male' }
});
whenever there will be an update request i want this gender to automatically set Male as default but i found that default don't set on update request.
(Note: It's just an example not a real life scenario. i just want mongoose default work if field is not present or null)
Is there any way in which i can set default on the updation of document ?
If you are using a function like update(), then this is not directly possible as stated by this answer. Still, you can simply switch to a function like findOne() and use save(), which should do the same.
When upserting documents, you can also check out the setDefaultsOnInsert option: https://mongoosejs.com/docs/defaults.html#the-setdefaultsoninsert-option
const options = {
// Create a document if one isn't found. Required
// for `setDefaultsOnInsert`
upsert: true,
setDefaultsOnInsert: true
};
await XY.findOneAndUpdate(query, update, options);

One way association with array of reference _id in sails js

I'm trying to use one way association because I need only to have reference from 1 model to other model but not vice versa.
Model Arts:
module.exports = {
attributes: {
fileName: {type: 'string', required: true},
softwareUsed: {
model: 'Softwares'
}
}
}
Model Softwares:
module.exports = {
attributes: {
name: {type: 'string', required: true}
}
}
This is my api:
http://localhost:1337/api/v1/arts/create
if this is my request body, it works fine:
request body:
{
"fileName": "booking.jpeg",
"softwareUsed": "5e70309cbf12b61299d6c528",
}
but i want to store array of softwareUsed, so i tried:
request body:
{
"fileName": "booking.jpeg",
"softwareUsed": ["5e70309cbf12b61299d6c528", "5e70309cbf12b61299d6c529"],
}
but i got an error with that:
error: OperationalError [UsageError]: Invalid new record.
Details:
Could not use specified `softwareUsed`. Expecting an id representing the associated record, or `null` to indicate there will be no associated record. But the specified value is not a valid `softwareUsed`. Instead of a string (the expected pk type), the provided value is: [ '5e70309cbf12b61299d6c528', '5e70309cbf12b61299d6c529' ]
I also tried to make it array in model:
softwareUsed: [{
model: 'Softwares'
}]
but still don't work.
Is there a way to that in one way association or I need to use other association, but how can I achieve that?
Thank you.
I think you need to label the softwareUsed attribute with a collection, not a model:
module.exports = {
attributes: {
fileName: {type: 'string', required: true},
softwareUsed: {
collection: 'Softwares'
}
}
}
All the documentation on one-to-many in the sails docs involves two-way associations and adding a via attribute, but I think this way works for a one-way association.
Of course, your first api call may now longer work: you may need to wrap the single software id in an array.

how to multi ref in mongoose

I am trying to ref two documents in one property, i have been checking the oficial documentation but i didn't get the solution...
At the moment i am trying this...
items: [{
type: mongoose.Schema.Types.ObjectId,
ref: ['items','users']
}],
In the documentation they mention refPath... but i could not populate both models... any solution for this?
// LINK TO DOCUMENTATION
https://mongoosejs.com/docs/populate.html#dynamic-ref
You don't need to pass refs in arrays. Here is the simple solution:
Mongoose Model (Report.js):
You can clearly see that I did not pass any ref to my Model but still, you can use multiple refs in post/get APIs. I will show you next.
const mongoose = require('mongoose');
const reportSchema = new mongoose.Schema({
reportFrom : {
type: mongoose.Schema.Types.ObjectId,
require: true,
},
reportTo: {
type: mongoose.Schema.Types.ObjectId,
require: true,
},
}
);
module.exports = mongoose.model("report", reportSchema);
Above "reportTo" means the Id of someone post whom the user is going to report or the id of user profile whom the user is going to report. Means "reportTo" may be an ID of User Profile or Post. So, if "reportTo" contains user Id then I have to refer to users collection but if "reportTo" contains post Id then I have to refer to posts collection. So, how I can use two refs. I will simply pass type query from postman to tell which ref to go either posts or users. See below my API request:
APIs file (reports.js)
const reports = req.query.type === "Post" ? await Report.find({reportTo: req.params.id}).populate({
path: 'reportFrom', // attribute name of Model
model: "User", // name of model from where you want to populate
select: "name profilePicture", // get only user name & profilePicture
}).populate({
path: 'reportTo', // attribute name of Model
model: "Post",
}).sort({ _id: -1 })
: req.query.type === "Profile" ? await Report.find({reportTo: req.params.id}).populate({
path: 'reportFrom', // attribute name of Model
model: "User",
select: "name profilePicture",
}).populate({
path: 'reportTo', // attribute name of Model
model: "User",
select: "name profilePicture",
})
.sort({ _id: -1 })
: null
return res.status(200).json(reports);
See the line 7 & 15, you can clearly see how I use two different refs for same attribute. In first case, reportTo is refered to Post Model & in second case reportTo is refered to User Model.

Remove undesirable fields from being returned in sails

I am new to SailsJS and stuck in Data Model as follows:
I have a User model as follows:
module.exports = {
attributes: {
firstName: {
type: 'string'
},
email: {
type: 'email',
required: true
},
password: {
type: 'String'
},
passwordSalt: {
type: 'String'
},
projects:{
collection: 'ProjectMember',
via: 'userId'
}
}
};
Task Model :
module.exports = {
taskName: {
type: 'String'
},
userId: {
model: 'User'
}
};
In Task model, it is getting all fields from User table which is not required while task data is rendered. I was planning to create one more model called TinyUser which stores only required fields to be shown when task data is rendered.
But TinyUser should just refer User table and get required data from it rather than we creating all data for TinyUser manually when user data is created.
Is there any way this can be achieved in Sails?
Thanks in Advance..
I'm not sure about your question, but this will return a list of required attributes for any model
_.find(sails.models.<YOUR MODEL>._attributes, function(attr){return attr.required})
If your intent it to simply remove undesirable fields you can override the toJSON / toObject methods
see
https://github.com/balderdashy/waterline-docs/blob/master/models.md#toobjecttojson-instance-methods
User.find({select:['firstName', 'email']}).exec()

Mongoose: How to model a foreign key/inverse relationship?

I am using Mongoose to model Person and Transaction collections, where each Transaction will have references to two different Person instances:
var TransactionSchema = new Schema({
, amount : { type: Number, required: true }
, from : { type: ObjectId, required: true }
, to : { type: ObjectId, required: true }
, date : Date
});
var PersonSchema = new Schema({
name : { type: String, required: true }
, transactions : [ObjectId]
});
I'd like each Person to have a collection of all the Transactions that they are either the to or from value for. So far, this is the best way I've been able to figure out how to do it:
TransactionSchema.pre('save', function(next, done) {
var transaction = this;
Person.findById(this.to, function (err, person) {
person.transactions.push(transaction);
person.save();
});
Person.findById(this.from, function (err, person) {
person.transactions.push(transaction);
person.save();
});
next();
});
This seems excessive. Is there a better way to do it, or am I trying to use MongoDB too much like a relational database? Instead of having a collection of Transactions associated with each Person instance, should I just be querying the Translation collection directly?
Thank you.
You've got to think more on the queries you are going to execute on the database when you design the MongoDB schema.
Try to duplicate data for speed and reference it for integrity. What does that mean?
Well, for example when you make a query for a Transaction, I guess you don't need all the user details from the first time no? (do you need the user's email, location when displaying info on a Transaction?)
I think you just probably need the user id and the username, so you should do something like this:
var TransactionSchema = new Schema({
, amount : { type: Number, required: true }
, from : {
user_id: {
type: ObjectId
, required: true
}
, username: {
type: String
, required: true
}
}
, to : {
user_id: {
type: ObjectId
, required: true
}
, username: {
type: String
, required: true
}
}
, date : Date
});
So instead of doing 3 queries for the page displaying the Transaction details (one for the transaction and 2 additional queries for the usernames), you'll have just one.
This is just an example, you could apply the same logic for the User schema, depending on what you're trying to achieve.
Anyway I don't think your middleware is ok, since you are not checking for errors there (you are always calling next no matter what). This is how I would write the middleware (didn't test, but the idea is important):
TransactionSchema.pre('save', function(next, done) {
var transaction = this;
Person.where('_id').in([this.to, this.from]).run(function (err, people) {
if (people.length != 2) { next(new Error("To or from doesn't exist")); return; }
Step(
function save_to() {
people[0].transactions.push(transaction);
people[0].save(this);
},
function save_from(err) {
if (err) { next(err); return; }
people[1].transactions.push(transaction);
people[1].save(this);
},
function callback(err) {
next(err);
}
);
});
});
In the code above I'm using the Step library for flow control and I'm only using one query instead of two (when searching for "to" and "from").