Force database tablename for collection relationship in Sails - node.js

In a sails project, considering a model User and a model Role, with a relationship between User and Role :
// `User.js
module.exports = {
attributes: {
...
roles: {
collection: 'role',
dominant: true
},
...
}
}
For the the database representation, sails/waterline will create following tables :
table user,
table role,
table like user_roles__role_roles_role to represent the collection
I know we can force the name for the models USER and ROLE
(with the property 'tablename' : http://sailsjs.com/documentation/concepts/models-and-orm/attributes).
But how can we force the name the relationship table ? (Especially this name is quite long and tends to exceed limit).

Assuming this is a two-way relationship, and the Role model has a users collection, Sails will expect a table named role_users__user_roles, which has the role id first, user id second.
Your example table name would require User to be dominant and would require the Role model to have an attribute named roles_role that is a User collection.
To create your own join table, you can use the through association method and add a new model that represents the relationship, perhaps UsersRoles, and specify the tableName in that model definition.
Examples of the through association:
sails
docs
similar question
gist from comments in that question

Related

Sequelize: Defining Associations

Reading the documentation of Sequelize I'm in some level confused, what Sequelize will provide automatically for us and what we need to explicitly tell it.
I have two models: User and Post. As you have guessed a User can have multiple Posts and a Post belongs only to one User. Setting the respective relationships will look so:
Post.associate = (models) => {
Post.belongsTo(models.users, {
as:'user',
foreignKey: {
name: 'user_id',
allowNull: false
}
}
}
User.associate = (models) => {
User.hasMany(models.posts, {
as:'posts',
onDelete:'CASCADE',
onUpdate:'CASCADE'
}
}
My question is: should I specify the foreignKey one more time when declaring the hasMany association, or it is enough for Sequelize to have the foreignKey in one of the declared relationships between two models (in the example - belongsTo)?
From what I think happens:
Sequelize goes through all your association one by one
If you already provided a foreign key name then fine
Else it will guess/name the foreign key on its own
Like what it says about options.foreignKey in docs e.g. for belongsTo : https://sequelize.org/master/class/lib/model.js~Model.html#static-method-belongsTo (same description for hasOne, hasMany, belongsToMany )
options.foreignKey || string OR object || optional
The name of the foreign key attribute in the source table or an object representing the type definition for the foreign column (see Sequelize.define for syntax). When using an object, you can add a name property to set the name of the column. Defaults to the name of target + primary key of target
If sequelize is guessing your foreignKey names then you will face issues only if your foreignKey name is not matching (tableName + Id) OR (tableName + _ + id)
💡 Hence, better to give foreignKey names on your own to both sides of associations to never face any issues going further.

referencing a model that hasn't been created, avoid the error: relation "TABLENAME" does not exist

I am very new to sequelize and postgresql. I looked a lot for an answer to this issue but I couldn't find anything related to my it. If this question is has an answer in another post I apologize for repeating and I'll gladly refer to the other post and remove my post from here.
At the beginning I did:
sequelize model:generate --name user --attributes username:string,email:string,password:string,collection_id:INTEGER
where collection_id references another table ID. I did the same for the collection table:
sequelize model:generate --name collection --attributes plant_id:integer,user_id:integer
in my models I updated the association
in the collection model I added:
models.collection.belongsTo(models.user, {foreignKey: 'user_id'})
and in the user model I added:
models.user.hasOne(models.collection, {foreignKey: 'collection_id'})
And in the migration files I added:
user_id: {
type: Sequelize.INTEGER,
references: {
model: {
tableName: 'user'
}
}
}
collection_id: {
type: Sequelize.INTEGER,
references: {
model: {
tableName: 'collection'
}
}
}
in their respective files. Now the issue here is that sequelize is trying to migrate the user model before the collection model, however the later is referenced in the first model and I am getting this error "relation "collection" does not exist" which only makes sense. Now if I removed the reference, is there a way to add it later after I migrate?
reference sets a foreign key on the table under the hood. But you can't have 2 tables with foreign keys pointing to each other (circular reference).
You set a foreign key on the "child" table which points to the "parent" table. In your case, if collection belongs to user then collection is "child".
You can read this paragraph from Sequalize docs with some details about the difference between hasOne and belongsTo (and read about those associations in Sequalize separately as well). {foreignKey: 'collection_id'} which you pass to hasOne method is actually supposed to be in collections table (target model), means in your case it doesn't make sense and should be like this instead:
models.user.hasOne(models.collection, {foreignKey: 'user_id'})
So, you don't need collection_id column in your user table, can safely remove it.

Mongoose: ref custom field name

I am configuring Mongoose to work on an existing MongoDB, that has these two collections:
Users - with fields:
_id: ObjectId
name: String
org_id: ObjectId
Organizations - with fields:
_id: ObjectId
name: String
I want to be able to populate a User document by Organization data.
So I've created these two Models:
const userSchema = new Schema({
name: String,
org_id: {
type: Schema.Types.ObjectId,
ref: 'Organization',
},
});
const User = mongoose.model('User', userSchema);
const organizationSchema = new Schema({
name: String,
code: String,
});
const Organization = mongoose.model('Organization', organizationSchema);
Since historically the ref field from User to Organization is called org_id (instead of just organization) the population of a user by the organization code is:
const user = await User.findById('5b213a69acef4ac0f886cdbc')
.populate('org_id')
.exec();
where user.org_id will be populated by Organization data. Of course I would be happier to have organization instead of org_id in both - populate method and the path (i.e. user.organizationd).
What is the proper way to achieve it without changing the existing documents?
I could create my Schema methods (instead of populate) and aliases, but I am looking for a more generic and elegant solution.
I understood that you don't want to change the existent documents, but for me, if this name of field doesn't make more sense you need to refactor.
Change the name of the field, organization instead of org_id.
For this you can use the $rename command: MongoDB $rename
db.getCollection('users').updateMany({},{$rename: { "org_id": "organization" }});
After this you will can call .populate('organization').
If it is impossible, I believe that you will not find a solution better than aliases.
Mongoose Documentation: Aliases
I will follow along your code.looks like you applied this: mongoose.Schema=Schema
you embedded Organization model into User. first lets extract organization details for each user.
//import User and Organization models
const main=async ()=>{
const user=await User.findById("placeUserId")//we get the user
const populated=await user.populate("org_id").execPopulate()//we populated organization with all properties
console.log(populated.org_id) }
in the above code, org_id was already referenced in the userSchema. we just reached org_id property and extracted. this was simple. next without changing any code in userSchema and organizationSchema i will find which user is in which organization with virtual property.
virtual property allows us to create virtual fields in the database. it is called virtual because we do not change anything. it is just a way that to see how two models are related.
for this we are gonna add a little code on the page where you defined you defined your organizationSchema file which i assume in models/organization.js. this code will describe the virtual field. it is kinda schema of the virtual field.
//models/organization.js
organizationSchema.virtual('anyNameForField',{
ref:"User", //Organization is in relation with User
localField:"_id"//field that Organization holds as proof of relation
foreignField:"org_id"//field that User holds as proof of relation
})
now time to write the function to find the user inside the organization.
const reverse=async ()=>{
const organization=await Organization.findById("")
const populated=await organization.populate("anyNameForField").execPopulate()
console.log(populated.anyNameForField) //i gave a stupid name to bring your attention.
}
very simple and elegant!

Retrieve data of a collection base on users session

Is it appropriate that my collections have a key call session so that I can identify from whom this data belongs to? For example, I have few sets of data that store books. How to identify in nosql DB(MongoDB) that a set of data belongs to which user? I know in mysql we simply design the table using Foreign Key, but how can I do it in nosql?
What I can think of is I will have these data :
{
bookId:1,
bookName: "soemthing",
userId:1
}
{
another_collection_key:1,
another_value: "soemthing",
userId:1
}
where every set of data will have userId, correct?
The best way is to create a user collection and a book collection. In each book collection add a list of type 'user'. Sample collections given below -
User{id,name}
Book{id,name,list<user>}
This way each book can store all the users who have that book.
The other way is to create 3 collections - books, users and a link collection for linking book and user.
Sample collections given below -
User { id,name }
Books { id,name }
Lnk_User_Book { User_Id,Book_Id }.

How to implemet a Many-to-Many relationship in Sequelizejs which also have relationship attributes?

So there are a lot of answers that explain how you model a Many-to-Many relationship in sequelizejs using hasMany() etc. But none of them has explained how and where do you store attributes which are created due to such an association, for eg: A customer can belong to or have many merchants and a merchant can have many customers, one such attribute of this relationship is a unique customer_id for a particular merchant-cutomer. Now where should this key(and any other detail) reside if we follow this: Stackoverflow answer
If you want additional attributes in your join table, you can define a model for the join table in sequelize, before you define the association, and then tell sequelize that it should use that model for joining, instead of creating a new one:
Customer = sequelize.define('customer', {})
Merchant = sequelize.define('merchant', {})
MerchantCustomers = sequelize.define('merchant_customers', {
customer_id: DataTypes.INTEGER
})
Merchant.belongsToMany(Customer, { through: MerchantCustomers })
Customer.belongsToMany(Merchant, { through: MerchantCustomers })
customer.addMerchant(merchant, { customer_id: 42 })
http://docs.sequelizejs.com/en/latest/docs/associations/#belongs-to-many-associations
To access the join table attributes:
c.getMerchants().then(function (merchants) {
merchants[0].merchant_customer.customer_id // Or perhaps merchant_customers, can't remember
});

Resources