Remove undesirable fields from being returned in sails - mongodb

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()

Related

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.

LoopBack4 MongoDB Auto Increment custom ID

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.

How to add record to sailsjs through association using the Blueprint REST API

I need to implement a through association in order to have a many-to-many relationship with a custom field in the join table. In SailsJS through associations require three models; two models are your business objects, Species and Lands in my case, and a third representing the join table SpeciesLands. See my SailsJS models at the bottom of this post.
Once I have the association set up how do I go about associating two objects through the blueprint API? Do I need to POST data to the /specieslands endpoint using the id of the Species and Land I want to link? Is it possible to create and link objects at the same time like you can with the many-to-many relationship? Is this something that needs to be done in a SailsJS controller rather than through the blueprint API?
Here are my models:
Species
module.exports = {
attributes: {
scientificName: {
type: 'string',
required: true,
unique: true
},
commonName: {
type: 'string'
},
taxon: {
type: 'string',
required: true
},
leadOffice: {
type: 'string'
},
lands: {
collection: 'lands',
via: 'land',
through: 'specieslands'
}
}
};
Lands
module.exports = {
attributes: {
agency: {
type: 'string',
required: true
},
name: {
type: 'string',
required: true,
unique: true
},
species: {
collection: 'species',
via: 'species',
through: 'specieslands'
}
}
};
SpeciesLands
module.exports = {
attributes: {
species: {
model: 'species'
},
land: {
model: 'lands'
},
population: {
type: 'string',
required: true,
enum: ['O', 'O+', 'P', 'U'] // THIS IS THE REASON FOR ASSOCIATION
}
}
};
I do not think it is possible like that.
I am pretty sure you have to:
Create Specie
Lands
Relation
1 & 2 using POST /modelName
3 using http://sailsjs.com/documentation/reference/blueprint-api/add-to
When using a through association, the Blueprint API will expect associations to be generated by POST into the associative model (The relation model).
In your case, if you wanted to associate an item from the Species model with an item from the Lands model, you would do something like this:
curl --header "Content-Type: application/json"
--request POST
--data '{species:15,land:51,population:"O"}'
http://www.example.com/SpeciesLands
Or in Postman:

sailsjs / waterline query "where" not empty

Hy there,
Before going to the hacky / cutom way i wanted to know if there is a built in query way to check for an empty / non empty many to many relationship as i was not successfull neither on google nor the doc.
If i take the example in the doc let's imagine i want to retrive a user only if he has a a Pet or Retrive a Pet without any Owner through a query.
// 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 Pet
pets: {
collection: 'pet',
via: 'owners',
dominant: true
}
}
});
// A pet may have many owners
var Pet = Waterline.Collection.extend({
identity: 'pet',
connection: 'local-postgresql',
attributes: {
breed: 'string',
type: 'string',
name: 'string',
// Add a reference to User
owners: {
collection: 'user',
via: 'pets'
}
}
});
P.s. i know how to filter results after query execution that's not what i'm asking :)
There is nothing built in (aka User.hasPet() ) or something like that., so the simple answer is NO
If I know of these issues before hand I tend to write my DB in such a way that the queries are fast. IE: the User schema would have a hasPets column. Whenever a pet is added/removed a callbacks hits the user table to mark that field if it has an owner or not. So then I can query User.findAll({hasPet:true}).
Its a little much, but it depends on where you speed is needed.
This is a bit late, but I wanted to let you know it's pretty easy to do this with the Waterline ORM lifecycle functions. I've done it in a few of my projects. Basically, use the beforeCreate and beforeUpdate functions to set your flags. For your user, it might look like...
var User = Waterline.Collection.extend({
identity: 'user',
connection: 'local-postgresql',
beforeCreate: function(values, next) {
if (values.pets) {
values.has_pet = true;
} else {
values.has_pet = false;
}
next();
}
beforeUpdate: function(values, next) {
if (values.pets) {
values.has_pet = true;
} else {
values.has_pet = false;
}
next();
}
attributes: {
firstName: 'string',
lastName: 'string',
// Add a reference to Pet
pets: {
collection: 'pet',
via: 'owners',
dominant: true
},
has_pet: {
type: 'boolean'
}
}
});
Then you can query based on the has_pet attribute

Sailsjs add one to many association to a model during beforeCreate

I am trying to give a default association from a user to a pet, whenever a new User created.
Model:: User.js
var User = {
attributes: {
name: {type: 'string'},
// Add a One Way Relation to pet model
pets: {
collection: 'pet'
},
},
/*** This did not work ***/
beforeCreate: function (user, next) {
var defaultPet = {name: 'Default Pet 1'};
Pet.find(defaultPet).exec(function(err, pet) {
user.name = "BEFORECREATE",
user.pets = pet[0].id;
next();
});
},
}
module.exports = User;
However when a new record is created the user.pet is [ ], but user.name is changed to "BEFORECREATE".
How do I get user.pets = [{name: 'Default Pet 1'}] automatically for the new user created?
Or is there a better place for setting such defaults?
----- UPDATE: More info
I am using sails-disk currently.
Model: Pet.js
module.exports = {
attributes: {
name: {
type: 'string',
required: true
}
}
};
You can't add associations to a model in a lifecycle callback like beforeCreate. Currently, Waterline looks for and processes "nested models" like these before lifecycle callbacks run, so by the time beforeCreate is called it's too late. The simplest solution would be to create a User.createUser class method that wraps the logic you want:
createUser: function(data, cb) {
// If there are already pets specified, just use those
if (data.pets) {return User.create(data).exec(cb);}
// Otherwise look up the default pet
Pet.findOne({name:"Default Pet 1"}).exec(function(err,pet) {
// Return in case of error
if (err) {return cb(err);}
// Assuming the default pet exists, attach it
if (pet) {
console.log("SETTING DEFAULT PET", pet.id);
data.pets = [pet.id];
}
// Create the pet
return User.create(data).exec(cb);
});
}
A few notes:
In your example you were setting pets directly to an ID, but since it's a collection you must set it to an array.
If you're using the sails-disk adapter, you'll need to set schema: true in your model for this to work.
The new User model you get back will not be populated; you'll have to do a find with a populate('pets') with the new User ID to get the pet data attached.