I read the Bookshelf documentation related to through, but, but I can't found out how I should proceed. I have three tables named in a different convention than Bookshelf uses. Basically, a Group has many Users through Profile. The last one that makes the connection.
Table Name: User
- id_user
- username
- password
Table Name: Profile
- id_user
- id_group
Table Name: Group
- id_group
- name
- description
- status
My group model is like so:
module.export = BookShelf.model('Group', {
tableName: 'pats_grupos',
users: function() {
return this.hasMany('User').through('Profile');
}
});
Taking in consideration that my tables don't follow the _id convention (but instead, the id_ one), how can I tell Bookshelf to work with my custom table naming pattern?
Accordingly to Bookshelf's idAttribute documentation, when not using the default 'id' you must change your models to explicitly declare the id attribute used. Like:
module.export = BookShelf.model('Group', {
tableName: 'pats_grupos',
idAttribute: 'id_group',
users: function() {
return this.hasMany('User').through('Profile');
}
});
And since your foreign keys are also not following Bookshelf default naming you may have to declare them explicitly on the through() call too. Something like:
//...
users: function() {
return this
.hasMany('User')
.through('Profile', 'id_user', 'id_group');
}
//...
Related
It seems i have misunderstood sequelize .hasMany() and .belongsTo() associations and how to use them in service. I have two models:
const User = db.sequelize.define("user", {
uid: { /*...*/ },
createdQuestions: {
type: db.DataTypes.ARRAY(db.DataTypes.UUID),
unique: true,
allowNull: true,
},
});
const Question = db.sequelize.define("question", {
qid: { /*...*/ },
uid: {
type: db.DataTypes.TEXT,
},
});
Given that one user can have many questions and each question belongs to only one user I have the following associatons:
User.hasMany(Question, {
sourceKey: "createdQuestions",
foreignKey: "uid",
constraints: false,
});
Question.belongsTo(User, {
foreignKey: "uid",
targetKey: "createdQuestions",
constraints: false,
});
What I want to achieve is this: After creation of a question object, the qid should reside in the user object under "createdQuestions" - just as the uid resides in the question object under uid. What I thought sequelize associations would do for me is to save individual calling and updating the user object. Is there a corresponding method? What I have so far is:
const create_question = async (question_data) => {
const question = { /*... question body containing uid and so forth*/ };
return new Promise((resolve, rejected) => {
Question.sync({ alter: true }).then(
async () =>
await db.sequelize
.transaction(async (t) => {
const created_question = await Question.create(question, {
transaction: t,
});
})
.then(() => resolve())
.catch((e) => rejected(e))
);
});
};
This however only creates a question object but does not update the user. What am I missing here?
Modelling a One-to-many relationship in SQL
SQL vs NoSQL
In SQL, contrary to how it is in NoSQL, every attribute has a fixed data type with a fixed limit of bits. That's manifested by the SQL command when creating a new table:
CREATE TABLE teachers (
name VARCHAR(32),
department VARCHAR(64),
age INTEGER
);
The reason behind this is to allow us to easily access any attribute from the database by knowing the length of each row. In our case, each row will need the space needed to store:
32 bytes (name) + 64 bytes (department) + 4 bytes (age) = 100 byes
This is a very powerful feature in Relation Databases as it minimizes the time needed to retrieve data to Constant time since we knew where each piece of data is located in the memory.
One-to-Many Relationship: Case Study
Now, let's consider we have these 3 tables
Let's say we want to create a one-to-many relation between classes and teachers where a Teacher can give many classes.
We can think of it this way. But, this model is not possible for 2 main reasons:
It will make us lose our constant-time retrieval since we don't know the size of the list anymore
We fear that the amount of space given to the list attribute won't be enough for future data. Let's say we allocate space needed for 10 classes and we end up with a teacher giving 11 classes. This will push us to recreate our database to increase the column size.
Another way would be this:
While this approach will fix the limited column size problem, we no longer have a single source of truth. The same data is duplicated and stored multiple times.
That's why for this one-to-many relationship, we'll need to store the Id of the teacher inside this class table.
This way, we still can find all the classes a teacher can teach by running
SELECT *
FROM classes
WHERE teacherID = teacher_id
And we'll avoid all the problems discussed earlier.
Your relation is a oneToMany relation. One User can have multiple Questions. In SQL, this kind of relation is modelled by adding an attribute to Question called userId or Uid as you did. In Sequelize, this would be achieved through a hasMany or BelongsTo like this:
User.hasMany(Question)
Question.belongsTo(User, {
foreignKey: 'userId',
constraints: false
})
In other words, I don't think you need the CreatedQuestions attribute under User. Only one foreign key is needed to model the oneToMany relation.
Now, when creating a new question, you just need to add the userId this way
createNewQuestion = async (userId, title, body) => {
const question = await Question.create({
userId: userId, // or just userId
title: title, // or just title
body: body // or just body
})
return question
}
Remember, we do not store arrays in SQL. Even if we can find a way to do it, it is not what we need. There must be always a better way.
I'm having trouble understanding how to retrieve information from a GraphQL Union. I have something in place like this:
const Profile = StudentProfile | TeacherProfile
Then in my resolver I have:
Profile: {
__resolveType(obj, context, info) {
if (obj.studentId) {
return 'StudentProfile'
} else if (obj.salaryGrade) {
return 'TeacherProfile'
}
},
},
This doesn't throw any errors, but when I run a query like this:
query {
listUsers {
id
firstName
lastName
email
password
profile {
__typename
... on StudentProfile {
studentId
}
... on TeacherProfile {
salaryGrade
}
}
}
}
This returns everything except for profile which just returns null. I'm using Sequelize to handle my database work, but my understanding of Unions was that it would simply look up the relevant type for the ID being queried and return the appropriate details in the query.
If I'm mistaken, how can I get this query to work?
edit:
My list user resolver:
const listUsers = async (root, { filter }, { models }) => {
const Op = Sequelize.Op
return models.User.findAll(
filter
? {
where: {
[Op.or]: [
{
email: filter,
},
{
firstName: filter,
},
{
lastName: filter,
},
],
},
}
: {},
)
}
User model relations (very simple and has no relation to profiles):
User.associate = function(models) {
User.belongsTo(models.UserType)
User.belongsTo(models.UserRole)
}
and my generic user resolvers:
User: {
async type(type) {
return type.getUserType()
},
async role(role) {
return role.getUserRole()
},
},
The easiest way to go about this is to utilize a single table (i.e. single table inheritance).
Create a table that includes columns for all the types. For example, it would include both student_id and salary_grade columns, even though these will be exposed as fields on separate types in your schema.
Add a "type" column that identifies each row's actual type. In practice, it's helpful to name this column __typename (more on that later).
Create a Sequelize model for your table. Again, this model will include all attributes, even if they don't apply to a specific type.
Define your GraphQL types and your interface/union type. You can provide a __resolveType method that returns the appropriate type name based on the "type" field you added. However, if you named this field __typename and populated it with the names of the GraphQL types you are exposing, you can actually skip this step!
You can use your model like normal, utilizing find methods to query your table or creating associations with it. For example, you might add a relationship like User.belongsTo(Profile) and then lazy load it: User.findAll({ include: [Profile] }).
The biggest drawback to this approach is you lose database- and model-level validation. Maybe salary_grade should never be null for a TeacherProfile but you cannot enforce this with a constraint or set the allowNull property for the attribute to false. At best, you can only rely on GraphQL's type system to enforce validation but this is not ideal.
You can take this a step further and create additional Sequelize models for each individual "type". These models would still point to the same table, but would only include attributes specific to the fields you're exposing for each type. This way, you could at least enforce "required" attributes at the model level. Then, for example, you use your Profile model for querying all profiles, but use the TeacherProfile when inserting or updating a teacher profile. This works pretty well, just be mindful that you cannot use the sync method when structuring your models like this -- you'll need to handle migrations manually. You shouldn't use sync in production anyway, so it's not a huge deal, but definitely something to be mindful of.
Sequelize gives you the ability to define a many to many association between tables which adds some extra functionality to a Model instance.
I have a Users table and I have defined a self-association on the table like so:
User.belongsToMany(models.User, { through: 'Friends', as: 'friends', foreignKey: 'userId' });
This gives the instance of the User model a couple of extra methods like user.getFriends(). So far so good.
What I want to do is to get all users who aren't friends of our instance. Something like user.getNonFriends(). Would that be possible using Sequelize?
A quick solution I can think of is, you could get the list of friends of the user A from the database. Using that result you can get the friends list that is not in the user's A list. Here is an example in code
const friends = user.getFriends();
const friendIds friends.map(friend => friend.id)
Friend.findAll({ where: {
id: { $notIn: [...friendIds] }
}
})
I saw a few questions with similar question/problem, but they are based on older versions of sequelize.js and unfortunately these answers cannot be applied to v.4.
I define two models: User and Tag. User can have multiple tags, tags can belong to one user (1:m relation). I have two separate files which define my models:
User.js file:
import Tag from './Tag';
const userFields = { /* my fields definitions... */ };
const User = sequelize.define('user', userFields);
User.hasMany(Tag, {
foreignKey: 'user_id',
constraints: true,
});
export default User;
Tag.js file:
import User from './User';
const tagFields = { /* tag fields definitions... */ };
const Tag = sequelize.define('tag', tagFields);
Tag.belongsTo(User, {
foreignKey: 'user_id',
constraints: false
});
export default Tag;
Obviously it doesn't work beacause these two files try to import each other and one of them see's other as undefined and I see error that User called with something that's not a subclass of Sequelize.Model.
Docs only give examples similar to the one below, where models are defined in one file:
const Player = this.sequelize.define('player', {/* attributes */});
const Team = this.sequelize.define('team', {/* attributes */});
Player.belongsTo(Team); // Will add a teamId attribute to Player to hold the primary key value for Team
So, my question is: how can I define associations between models in models' files? I know that one solution is to create a function (the one which will create associations) in a separate file and call it after models are defined. But I want to keep these associations with models' definitions. How can I do that in sequelize#v.4?
The difference in the library version does not matter. What actually matters is where you put the code responsible for declaring the associations. I know it is not very pretty, but what you have to do is to declare your associations outside both of the two files declaring the schemas.
This answers explains it and shows the example used by the library https://stackoverflow.com/a/36877672/614277
I am new to sails.js. I want to select all record from a table. How to use .find() .
Specially how waterline will know from which table i want data ? Because we are not mentioning any table name in model. I know there is .query(). But is this possible within waterline basic create / update / find / delete method ?
Another question how to use prefix for table name in sails.js ? Like i want to use sails_product as table name.
I am new to sails.js. I want to select all record from a table. How to use .find() .
If your model name is, for example, Book, you'd select all Book records with
Book.find()
.exec(function(err, books) {
if (err) return res.serverError();
console.log(books); // 'books' is an array of the found records
})
Specially how waterline will know from which table i want data ? Because we are not mentioning any table name in model. I know there is .query(). But is this possible within waterline basic create / update / find / delete method ?
Yes, it's possible. You don't have to deal with table names and such at all with waterline, all you need is your model name. Create, update, delete all work the same way as the find example above - so ModelName.actionName().
Another question how to use prefix for table name in sails.js ? Like i want to use sails_product as table name.
By default, waterline uses the model name lowercased as the corresponding table name. You can, however, overwrite this in your model settings. For example, if you have your model defined in a file called Book.js, its contents would look like this:
module.exports = {
attributes: {
name: {
type: 'String',
required: true
},
price: {
type: 'float'
}
},
tableName: 'custom_book_table'
}
This way the actual table created in the database will be called custom_book_table, while you'll still refer to your model in find queries etc. as Book.
Here's links to both Waterline and Sails docs to get you going. In model/query related issues, I'd definitely search from Waterline docs first.