I would like to find the parent table information of an object.
I have User hasMany Book
where Book has writer and assigned to user id.
Book has type, which is like fantasy, romance, history, scientific fiction... etc
So I want to find out the Book with type Scientific Fiction but not only for that, I also want the writer, which is User.
How can I find the book with its writer where where condition is given for books only? It seems like 'include' in Book.findAll( include: User) is not working; this tells me that include is only working for finding child tables not parent.
Here are some code for user
const User = sequelize.define('User', {
id: { type: DataTypes.STRING(6), field: 'ID', primaryKey : true }
}
associate: function(models) {
User.hasMany(models.Book, { foreignKey: 'userId' });
}
and book
const Book = sequelize.define('Book', {
id: { type: DataTypes.STRING(6), field: 'ID', primaryKey: true }, // primary key
userId: { type: DataTypes.STRING(6), field: 'USER_ID', primaryKey: true }
type: { type: DataTypes.STRING(20), field: 'TYPE'
}
Book has some more child table and I try to find those additional information in includes, so I guess I really need to find from Book.findAll(...)
Instead of User.findAll(include: Book).
Can anyone help?
I think I was making a mistake.
As soon as I changed
userId: { type: DataTypes.STRING(6), field: 'USER_ID', primaryKey: true }
to
userId: { type: DataTypes.STRING(6), field: 'USER_ID' }
include is working for belongsTo; and it finds the parent from child.
It seems like Sequelize has some problem with relation if there is more than one primary key declared in model...
If anyone does think this solves your problem, please share in reply so I can be more sure about it.
Thanks.
Related
So I'm building a backend with NestJs and Typegoose, having the following models:
DEPARTMENT
#modelOptions({ schemaOptions: { collection: 'user_department', toJSON: { virtuals: true }, toObject: { virtuals: true }, id: false } })
export class Department {
#prop({ required: true })
_id: mongoose.Types.ObjectId;
#prop({ required: true })
name: string;
#prop({ ref: () => User, type: String })
public supervisors: Ref<User>[];
members: User[];
static paginate: PaginateMethod<Department>;
}
USER
#modelOptions({ schemaOptions: { collection: 'user' } })
export class User {
#prop({ required: true, type: String })
_id: string;
#prop({ required: true })
userName: string;
#prop({ required: true })
firstName: string;
#prop({ required: true })
lastName: string;
[...]
#prop({ ref: () => Department, default: [] })
memberOfDepartments?: Ref<Department>[];
static paginate: PaginateMethod<User>;
}
As you might guess, one user might be in many departments and one department can have many members(users). As the count of departments is more or less limited (compared with users), I decided to use one way embedding like described here: Two Way Embedding vs. One Way Embedding in MongoDB (Many-To-Many). That's the reason User holds the array "memberOfDepartments", but Department does not save a Member-array (as the #prop is missing).
The first question is, when I request the Department-object, how can I query members of it? The query must look for users where the department._id is in the array memberOfDepartments.
I tried multiple stuff here, like virtual populate: https://typegoose.github.io/typegoose/docs/api/virtuals/#virtual-populate like this on department.model:
#prop({
ref: () => User,
foreignField: 'memberOfDepartments',
localField: '_id', // compare this to the foreign document's value defined in "foreignField"
justOne: false
})
public members: Ref<User>[];
But it won't output that property. My guess is, that this only works for one-to-many on the one site... I also tried with set/get but I have trouble using the UserModel inside DepartmentModel.
Currently I'm "cheating" by doing this in the service:
async findDepartmentById(id: string): Promise<Department> {
const res = await this.departmentModel
.findById(id)
.populate({ path: 'supervisors', model: User })
.lean()
.exec();
res.members = await this.userModel.find({ memberOfDepartments: res._id })
.lean()
.exec()
if (!res) {
throw new HttpException(
'No Department with the id=' + id + ' found.',
HttpStatus.NOT_FOUND,
);
}
return res;
}
.. but I think this is not the proper solution to this, as my guess is it belongs in the model.
The second question is, how would I handle a delete of a department resulting in that i have to delete the references to that dep. in the user?
I know that there is documentation for mongodb and mongoose out there, but I just could not get my head arround how this would be done "the typegoose way", since the typegoose docs seem very limited to me. Any hints appreciated.
So, this was not easy to find out, hope this answer helps others. I still think there is the need to document more of the basic stuff - like deleting the references to an object when the object gets deleted. Like, anyone with references will need this, yet not in any documentation (typegoose, mongoose, mongodb) is given a complete example.
Answer 1:
#prop({
ref: () => User,
foreignField: 'memberOfDepartments',
localField: '_id', // compare this to the foreign document's value defined in "foreignField"
justOne: false
})
public members: Ref<User>[];
This is, as it is in the question, the correct way to define the virtual. But what I did wrong and I think is not so obvious: I had to call
.populate({ path: 'members', model: User })
explicitly as in
const res = await this.departmentModel
.findById(id)
.populate({ path: 'supervisors', model: User })
.populate({ path: 'members', model: User })
.lean()
.exec();
If you don't do this, you won't see the property members at all. I had problems with this because, if you do it on a reference field like supervisors, you get at least an array ob objectIds. But if you don't pupulate the virtuals, you get no members-field back at all.
Answer 2:
My research lead me to the conclusion that the best solution tho this is to use a pre-hook. Basically you can define a function, that gets called before (if you want after, use a post-hook) a specific operation gets executed. In my case, the operation is "delete", because I want to delete the references before i want to delete the document itself.
You can define a pre-hook in typegoose with this decorator, just put it in front of your model:
#pre<Department>('deleteOne', function (next) {
const depId = this.getFilter()["_id"];
getModelForClass(User).updateMany(
{ 'timeTrackingProfile.memberOfDepartments': depId },
{ $pull: { 'timeTrackingProfile.memberOfDepartments': depId } },
{ multi: true }
).exec();
next();
})
export class Department {
[...]
}
A lot of soultions found in my research used "remove", that gets called when you call f.e. departmentmodel.remove(). Do not use this, as remove() is deprecated. Use "deleteOne()" instead. With "const depId = this.getFilter()["_id"];" you are able to access the id of the document thats going to be deletet within the operation.
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.
I am trying to update my collection which has an array field(initially blank) and for this I am trying this code
Industry.update({_id:industryId},
{$push:{categories: id:categoryId,
label:newCategory,
value:newCategory }}}});
No error is shown, but in my collection just empty documents({}) are created.
Note: I have both categoryId and newCategory, so no issues with that.
Thanks in advance.
This is the schema:
Industry = new Meteor.Collection("industry");
Industry.attachSchema(new SimpleSchema({
label:{
type:String
},
value:{
type:String
},
categories:{
type: [Object]
}
}));
I am not sure but maybe the error is occuring because you are not validating 'categories' in your schema. Try adding a 'blackbox:true' to your 'categories' so that it accepts any types of objects.
Industry.attachSchema(new SimpleSchema({
label: {
type: String
},
value: {
type: String
},
categories: {
type: [Object],
blackbox:true // allows all objects
}
}));
Once you've done that try adding values to it like this
var newObject = {
id: categoryId,
label: newCategory,
value: newCategory
}
Industry.update({
_id: industryId
}, {
$push: {
categories: newObject //newObject can be anything
}
});
This would allow you to add any kind of object into the categories field.
But you mentioned in a comment that categories is also another collection.
If you already have a SimpleSchema for categories then you could validate the categories field to only accept objects that match with the SimpleSchema for categories like this
Industry.attachSchema(new SimpleSchema({
label: {
type: String
},
value: {
type: String
},
categories: {
type: [categoriesSchema] // replace categoriesSchema by name of SimpleSchema for categories
}
}));
In this case only objects that match categoriesSchema will be allowed into categories field. Any other type would be filtered out. Also you wouldnt get any error on console for trying to insert other types.(which is what i think is happening when you try to insert now as no validation is specified)
EDIT : EXPLANATION OF ANSWER
In a SimpleSchema when you define an array of objects you have to validate it,ie, you have to tell it what objects it can accept and what it can't.
For example when you define it like
...
categories: {
type: [categoriesSchema] // Correct
}
it means that objects that are similar in structure to those in another SimpleSchema named categoriesSchema only can be inserted into it. According to your example any object you try to insert should be of this format
{
id: categoryId,
label: newCategory,
value: newCategory
}
Any object that isn't of this format will be rejected while insert. Thats why all objects you tried to insert where rejected when you tried initially with your schema structured like this
...
categories: {
type: [Object] // Not correct as there is no SimpleSchema named 'Object' to match with
}
Blackbox:true
Now, lets say you don't what your object to be filtered and want all objects to be inserted without validation.
Thats where setting "blackbox:true" comes in. If you define a field like this
...
categories: {
type: [Object], // Correct
blackbox:true
}
it means that categories can be any object and need not be validated with respect to some other SimpleSchema. So whatever you try to insert gets accepted.
If you run this query in mongo shell, it will produce a log like matched:1, updated:0. Please check what you will get . if matched is 0, it means that your input query is not having any matching documents.
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
I'm having a hard time trying to figure out if sails/waterline even does this.
(so an adequate answer would simply be if this is possible or not, I have been reading docs, looking through github issues and looking through code, but still not sure)
I have a one to one association setup where an 'account' has a 'contact'
I'm trying to create a contact within sails blueprints (so basically just using the create() method)
account =
{ name: 'Corp'
contact:{
firstName: 'Bob',
lastName: 'Jones'
}
}
so should Account.create(account).exec() create the account and the associated contact? Because I'm getting the following error
TypeError: Cannot convert null to object
My model is setup like so
account.js
module.exports = {
migrate: 'safe',
tableName: 'accounts',
autoPK: false,
attributes: {
id: {
type: 'INTEGER',
primaryKey: true,
autoIncrement: true
},
contactId: 'INTEGER',
name: {type: 'STRING', maxLength: 100},
contact: {
model: 'contact',
columnName:'contactId'
}
}
};
I'm using sails 10.0-rc8 / waterline 10.0-rc15
Creating an associated instance at the same time as its parent (aka "nested create") should work, but it's tricky to get things just right when you're dealing with a legacy database. In your case, the contactId attribute declaration is probably causing the issue, since Waterline expects the foreign key field to be implicit, not explicit. Try removing:
contactId: 'INTEGER',
entirely and see where that gets you.
After some research I found out that as of version 0.10.0-rc15 of waterline you can NOT have a customized foreign keys. In the above model if I change the "contactId" column to just "contact" (basically make it look exactly like it does in the docs. Then it works.
I made the following bug report
https://github.com/balderdashy/waterline/issues/529