Confused about Sequelize associations - node.js

I just found Sequelize as a good ORM framework to use in my node + MySQL webapp. But when I was designing my database tables, I got so confused about Sequelize associations.
Just think about user and system notification in a webapp as a very simple case:
The site has many users
The site would send notifications to some or every users, and the same notification just save as one record
Users could know any notification he/she received is read or not(by a read date)
Then I design 3 tables as expect:
User: id, name
Notification: id, title, content, sendAt
Notify: id, UserId(as receiver), NotificationId, readAt
I can simply define User and Notification models by Sequelize. But I just don't know how to use associations to make Notify relate to the 2 tables, by foreign key UserId and NotificationId.
I have tried to use hasOne associations like this:
Notify.hasOne(User);
Notify.hasOne(Notification);
Then there comes a NotifyId column in both User and Notification tables. And this is not as my expect. Maybe I thought a wrong way to use associations, so I wanna know how to use it correctly?
Additionally, if I want to get results as the JSON:
[
{id: 123, user: [Object some user], notification: [Object some notification], readAt: null},
{id: 124, user: [Object another user], notification: [Object some notification], readAt: "Mon Oct 29 2012 20:44:54 GMT+0800"}
]
how can I use find method query just once like in a SQL I used before:
SELECT
Notify.id as 'Notify.id',
User.name as 'User.name',
Notification.title as 'Notification.title',
Notification.sendAt as 'Notification.sendAt',
Notify.readAt as 'Notify.readAt'
FROM User, Notification, Notify
WHERE
Notify.UserId = User.id AND
Notify.NotificationId = Notification.id;

And I found right way. I realized that my Notify table is a entiry table because containing a readAt column. So the association should be defined as:
Notification.hasMany(db.Notify);
User.hasMany(db.Notify);
then everything became OK.
The second problem in find has been resolved too. Just use like this:
Notify.findAll({
include: ['User', 'Notification']
}).success(function (notify) {
console.log(notify.user);
console.log(notify.notification);
});

Related

Adding value to already declared MongoDB object with mongoose Schema

I am new to MongoDB and mongoose. I am trying to create a Node & MongoDB auction app. So since it is actually an online auction, users should be able to bid for items. I successfully completed the user registration, sign in page and authentication process, however, I am a bit stuck in the bidding page.
I created a Schema using mongoose and each item for auction is saved in the database. I want to add name and price of each user who bid for the item in the same object in MongoDB, like this:
{
name: "valuable vase from 1700s",
owner: "John Doe",
itemId: 100029,
bids: {
100032: 30000,
100084: 34000
}
}
So each user will have ids like 100032: 30000, and when they bid, their "account id: price" will be added under bids in the database object of the item.
I made some research and found some ways to solve the problem but I want to know if what I want to do is possible and if it is the right solution to do.
Thanks for giving me your time!
There are indeed couple of ways to achieve what you want.
In my opinion, a collection called ItemBids, where each document includes this data structure, will benefit you the most.
{
itemId: ObjectId # reference to the item document
accountId: ObjectId # reference to the account
bid: Number # the bid value
}
This pattern is suitable for your case because you can easily query the bids by whatever you want -
You can get all the account bids, you can get all the item bids, and you can sort them with native Mongo by the bid price.
Every time there's a bid, you insert a new document to this collection.
Another option is embedding an array of Bids objects in the item Object.
Each Bid object should include:
bids: [{
account: ObjectId("") # This is the account
price: Number
}]
The cons here are that querying it and accessing it will require more complex queries.
You can read more about the considerations
here:
https://docs.mongodb.com/manual/core/data-model-design
https://coderwall.com/p/px3c7g/mongodb-schema-design-embedded-vs-references
The way you decided to implement your functionality is a little bit complicated.
It is not impossible to do that but, the better way is to use array of objects instead of a single object like this:
{
name: '',
..
..
bids: [{
user: 100032,
price: 30000
}, {
user: 100084,
price: 34000
}]
}

How to define many to many with Feathers and Sequelize with additional fields in join table?

I am struggling to find the solution for that.
I want to have users which can belong to many organizations.
Each user can have a different role (I would prefer even roles but it sounds even more complicated...) at a specific organization.
In the table like User_Organization_Role I need to have fields like role (roleId?), isActive. Maybe some more.
I am using Feathers Plus generator but I do not think it matters in this case, however it may be beneficial to add something to the schema file?
I thought having belongsTo with simple organizationId field will be sufficient but I've realized that changing that to manyToMany, later on, would be painful so I think it is much better to implement that now.
I will appreciate any solutions / suggestions / best practices etc.
n:m relations are by far the most difficult to handle, and there's really no one-size-fits-all solution. The biggest thing is to read and understand this page and its sub-pages, and then read them 2 more times for good measure. Try to focus on doing one thing at a time. I outline how I would approach this with feathersjs in this issue:
https://github.com/feathersjs/feathers/issues/852#issuecomment-406413342
The same technique could be applied in any application... the basic flow goes like this:
Create or update your primary objects first (users, organizations, roles, etc.). There are no relations made at this point. You need to have your objects created before you can make any relations.
Create or update the relations. This involves updating a "join" table (aka: "mapping" or "through" table) with data from step #1. The join table can (and should) have its own model. It should contain a foreign key for each of the objects you are associating (userId, organizationId, roleId etc.). You can put other fields in this table too.
Here is some pseudo code for how I would define my models (only showing relevant code for brevity). There is a little more to it than what I describe below, but this should get you started.
const UserOrganizationRole = sequelize.define('User_Organization_Role', {
// Define any custom fields you want
foo: DataTypes.STRING
})
// Let sequelize add the foreign key fields for you.
// Also, save a reference to the relationship - we will use it later
User.Organization = User.belongsToMany(Organization, { through: UserOrganizationRole });
User.Role = User.belongsToMany(Role, { through: UserOrganizationRole });
Organization.User = Organization.belongsToMany(User, { through: UserOrganizationRole });
Role.User = Role.belongsToMany(User, { through: UserOrganizationRole });
... and here is how I would go about handling inserts
const user = await User.create({ ... });
const org = await Organization.create({ ... });
const role = await Role.create({ ... });
await UserOrganizationRole.create({
userId: user.id,
organizationId: org.id,
roleId: role.id,
foo: 'bar'
});
... and finally, load the data like so:
// Now we can reference those relationships we created earlier:
const user = await User.findById(123, {
include: [User.Organization, User.Role]
});
const org = await Organization.findById(456, {
include: [Organization.User]
});

Mongoose: How to find documents by sub-collection's document property value

I’m using Mongoose version 4.6.8 and MongoLab (MLab). I have a Mongoose schema called “Group” that has a collection of User subdocuments called “teachers”:
var GroupSchema = new Schema({
//…more properties here…//
teachers: [{
type: Schema.ObjectId,
ref: 'User'
}]
});
This is a document from the “groups” collection on MongoLab:
{
//…more properties here…//
"teachers": [
{
"$oid": "5799a9c759feea9c208c004c"
}
]
}
And this is a document from the “users” collection on MongoLab:
{
//…more properties here…//
"username": "bob"
}
But if I want to get a list of Groups that have a particular teacher (User) with the username of “bob”, this doesn’t work (the list of groups is empty):
Group.find({"teachers.username": "bob"}).exec(callback);
This also returns no items:
Group.find().where('teachers.username').equals('bob').exec(callback);
How can I achieve this?
Without some more knowledge of your set up (specifically whether you want anybody named Bob or a specific Bob whose id you could pick up first) - this might be some help although I think it would require you to flatten your teachers array to just their ID's, not single-key objects.
User.findById(<Id of Bob>, function(err, user){
Group.find({}, function(err, groups){
var t = groups.map(function(g){
if(g['teachers'].indexOf(user.id))
return g
})
// Do something with t
})
})
You can use populate to do that.
Try this:
Group.find({})
.populate({
path : 'teachers' ,
match : { username : "bob" }
})
.exec(callback);
populate will populate based on the teachers field (given path) and match will return only those who have username bob.
For more information on mongoose populate options, Please read Mongoose populate documentation.
I think the solution in this case is to get a teacher’s groups through the User module instead of my first inclination which was to go through the Groups module. This makes sense because it is in line with how modern APIs represent a one-to-many relationship.
As an example, in Behance’s API, an endpoint for a user’s projects is:
GET /v2/users/user/projects
And a request to this endpoint (where the User’s username is “matiascorea”) would look like this:
https://api.behance.net/v2/users/matiascorea/projects?client_id=1234567890
So in my case, instead of finding the groups by teacher, I would need to simply find the User (teacher) by username, populate the teacher’s groups, and use them:
User.findOne({username: 'bob'})
.populate('groups')
.exec(callback);
And the API call for this would be:
GET /api/users/user/groups
And a request to this endpoint would look like this:
https://example.com/api/users/bob/groups

Twitter OAuth + Node + A MongoDB Collection: How to store users?

I've successfully retrieved a user's id and screen name from Twitter's oauth service, like so:
{ user_id: '12345678', screen_name: 'spencergardner' }
I am hoping to create a simple way for users to authenticate using Twitter (and soon Facebook, for example), so that they can add words they are interested in learning to their account. How do I now go about setting up "users" in a mongodb collection that will allow each user to have their own bank of words (and other data)?
If I understand you correctly, you are asking how you can store data with different structures in a mongo collection.
Well, you're in luck! Mongo does just that. You can store any different data structures in a mongo collection without having to "declare" the structure a priori. Just create a DBObject (if using the Java driver for example), add fields to it, and just save it. You can then retrieve it, and query the data to see what this specific users has, and anything you want in your application.
I use mongoose with nodejs to create a user model which you would then input the oauth data into and then you would be free to associate whatever data you wanted.
Once you've obtained the Oauth information you could create a new User associating the twitter data with that specific user model. The _id is automatically provided however in this case, you would use the user_id returned from twitter (assuming that is unique).
Here's an example schema:
var mongoose = require('mongoose')
, Schema = mongoose.Schema
var userSchema = new Schema({
_id: String,
screen_name: String,
words: Array
});
module.exports = mongoose.model('User', userSchema);
In future you would be able to query the database for a particular user, and authenticate a user when they return. You would also look to create a new User with something similar to the following:
new User({ _id: req.body.user_id,
password: req.body.screen_name,
words: []
}).save(function(err) {
if (!err) {
res.send("User added");
}
})

CouchDB-river and related documents

I have a product which is owned by a user in my CouchDB.
product =
name: 'Laptop'
userId: somelongid
user =
username: 'James'
With views and include_docs=true it returns:
product =
name: 'Laptop'
user =
username: 'James'
( I know it doesn't exactly return the above but it's close enough )
I do this cause every time I need a product I also need the owner (to link to his page). At first I thought I would just use include_document=true on the _change feed but of course that does something else.
So how can I get the related user when getting product results?
One solution is to collect all the userIds from the search result and query the _all_docs view in couchDb to get the users.
use a view (f.E. "userByDocId") that emits (doc._id,doc.user)
and do a query userByDocId?key="Username"

Resources