Set a field 'unique' ONLY within a user (mongoose) - node.js

I have a standard mongoose User Scheme, and a 'Uniform' scheme that holds dress items in the following way:
mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
items: { type: [String], unique: true }
})
Thing is, I want the items in the 'items' array to be unique only in the scope of the user. Meaning that I don't want a user to have serval uniforms with the same item. But I don't have any problem with other users having the same items as my user.
The current scheme prevents ANY uniform to have the same values inside 'items', I want it to be just within the scope of the user.
Can that even be done?
(In ruby on rails, the line that creates this behavior is: validates_uniqueness_of :items, scope: [:user])

If User and Uniform are different schemas (different collections) then having uniqueness achieved between keys of different collections cannot be done in mongodb.
We can have uniqueness achieved by combining different keys of a same collection using Compound Index

Related

storing array of strings in postgres using sequelize?

I currently have a posts table and a users table.
My Posts model:
id: {
type: Sequelize.INTEGER,
},
title: {
type: Sequelize.STRING,
},
images: {
type: Sequelize.STRING,
},
My Users model:
id: {
type: Sequelize.INTEGER,
},
name: {
type: Sequelize.STRING,
},
Each user can have many posts and each post can only have 1 user.
Association:
Users.hasMany(Posts, {
foreignKey: "id",
});
Posts.belongsTo(Users, {
foreignKey: "id",
});
In my posts table images is a string, a user can post many images in one post (hence an array of filename of images).
in my post method, I am using JSON.stringify to store the images filename as a string in the database as without JSON.stringify i get an error saying images cannot be an object or array. After hours of research i found that it is not best practise to use JSON.stringify on an array.
If that is correct, then how should i store my array of filename of images in Postgres using sequelize?
In my posts table images is a string, a user can post many images in one post (hence an array of filename of images).
Excuse me if I get preachy about this, but you should mend your evil ways and return to the golden path of normalized relational data modelling.
It may seem intuitive to store a relationship as an array of "foreign keys", but I promise you that you will suffer in the long run:
you cannot enforce the relationship with a constraint, which leads to inconsistencies
any attempt to join the tables via that relationship will lead to unnecessarily complicated SQL statements that won't perform and scale well
adding a new relationship means modifying (that is, rewriting) a potentially large table row
Use a "junction table" to model such relationships: such a table consists of two foreign keys, each pointing to one of the tables that are related. An entry means that the two referred rows are related.
If an image can only belong to a single post, the solution is even simpler: add a foreign key constraint to the images table.

How can I count the view of a Specific post by a User ? Count every User just Once

We have user and news model, in the news model we have e viewsCount field, I want to increment this view count when a GET request is made by a User.
When a specific user makes a GET request, the view count will increment one, every user just one view.
const NEWSModel = new Schema({
viewesCount: { type: Number },
Publisher: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
LikesCount: { type: Number },
DislikeCount: { type: Number },
Comments: CommenTs
});
Every user can view the news as many times as wants, but just can make one view. How can I do that?
you Can change your model like and then whenever you get a news just push the user id to the viewedBy field.
news.viewedBy.push(user id)
viewedBy: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}]
}); ```
If you have not a lot of users, you can add additional field to news model like users_viewed which would be array of unique user ids.
And make additional check before incrementing views count.
If user, who requested news is already in this users_viewed array, you skip any additional actions.
If don't, increment views counter.
But if you do have a lot of users, it's better to store views counter in Redis to skip request to database and increment in memory counter.
The logic for storing and showing data would be the same, but you'll reduce load on your database and speed up the whole process.
[UPDATE] According to your comment, about number of users.
To make things work you can use this package.
First of all, after request for a news from a client, you can store all the news data in your cache (to reduce number of requests to your database).
Now you have few possible ways to handle number of views.
I think, the easiest to implement would be to add user unique identifier to SET. And return number of users in SET using SCARD;
In this solution you wouldn't need to check if user already watched the news, because set data structure holds only unique values (the same reason why do we need to use user's unique identifier).
And you just use 2 redis requests, which is pretty good for heavy load services.
You can have another field called viewedBy of type array in which you can store users ids. Then it will be easier to check if a user already viewed your post or to count them.
File: news.model.js
const News = new Schema({
viewedBy: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}],
// other properties...
});
File: news.controller.js
const user = User.find({...}); // get current user
const news = News.find({...}); // get a news
/*
Update views count by adding the current user id if it's not already added
Thanks to '$addToSet', the update() function will do nothing if the user id it's already there)
*/
news.update({ $addToSet: { viewedBy: user._id } });
// Getting the views count
console.log('Total views:', news.viewedBy.length);
More about $addToSet: https://docs.mongodb.com/manual/reference/operator/update/addToSet/

Mongoose search for an object's value in referenced property (subdocument)

I have two schemas:
var ShelfSchema = new Schema({
...
tags: [{
type: Schema.Types.ObjectId,
ref: 'Tag'
}]
});
var TagSchema = new Schema({
name: {
type: String,
unique: true,
required: true
}
});
I would like to search for all Shelves where the tags array has a tag with a specific value.
I have tried using:
modelShelf.find({tags 'tags.name': 'mytag'})...
but it does not work. It always returns an empty array.
Any idea?
Looking at db each Shelf instance links only the objectID of the tags.
I have used references because I need to work also with Tag(s) entities.
In mongoDB you essentially can't do this directly as queries target a single collection at a time. Recently there were added new features which allow some kind of join when using the aggregation framework but for your needs that is not necessary.
From your schemas I see that the tags' names are unique so you can first fetch your desired tag with something like
modelTag.find({name: 'mytag'})
in order to get the tag's ID and then query your shelf collection for this tag ID
modelShelf.find({tags: tagId})

MongoDB/Mongoose index make query faster or slow it down?

I have an article model like this:
var ArticleSchema = new Schema({
type: String
,title: String
,content: String
,hashtags: [String]
,comments: [{
type: Schema.ObjectId
,ref: 'Comment'
}]
,replies: [{
type: Schema.ObjectId
,ref: 'Reply'
}]
, status: String
,statusMeta: {
createdBy: {
type: Schema.ObjectId
,ref: 'User'
}
,createdDate: Date
, updatedBy: {
type: Schema.ObjectId
,ref: 'User'
}
,updatedDate: Date
,deletedBy: {
type: Schema.ObjectId,
ref: 'User'
}
,deletedDate: Date
,undeletedBy: {
type: Schema.ObjectId,
ref: 'User'
}
,undeletedDate: Date
,bannedBy: {
type: Schema.ObjectId,
ref: 'User'
}
,bannedDate: Date
,unbannedBy: {
type: Schema.ObjectId,
ref: 'User'
}
,unbannedDate: Date
}
}, {minimize: false})
When user creates or modify the article, I will create hashtags
ArticleSchema.pre('save', true, function(next, done) {
var self = this
if (self.isModified('content')) {
self.hashtags = helper.listHashtagsInText(self.content)
}
done()
return next()
})
For example, if user write "Hi, #greeting, i love #friday", I will store ['greeting', 'friday'] in hashtags list.
I am think about creating an index for hashtags to make queries on hashtags faster. But from mongoose manual, I found this:
When your application starts up, Mongoose automatically calls
ensureIndex for each defined index in your schema. Mongoose will call
ensureIndex for each index sequentially, and emit an 'index' event on
the model when all the ensureIndex calls succeeded or when there was
an error. While nice for development, it is recommended this behavior
be disabled in production since index creation can cause a significant
performance impact. Disable the behavior by setting the autoIndex
option of your schema to false.
http://mongoosejs.com/docs/guide.html
So is indexing faster or slower for mongoDB/Mongoose?
Also, even if I create index like
hashtags: { type: [String], index: true }
How can I make use of the index in my query? Or will it just magically become faster for normal queries like:
Article.find({hashtags: 'friday'})
You are reading it wrong
You are misreading the intent of the quoted block there as to what .ensureIndex() ( now deprecated, but still called by mongoose code ) actually does here in the context.
In mongoose, you define an index either at the schema or model level as is appropriate to your design. What mongoose "automatically" does for you is on connection it inpects each registered model and then calls the appropriate .ensureIndex() methods for the index definitions provided.
What does this actually do?
Well, in most cases, being after you have already started up your application before and the .ensureIndexes() method was run is Absolutely Nothing. That is a bit of an overstatement, but it more or less rings true.
Because the index definition has already been created on the server collection, a subsesquent call does not do anything. I.e, it does not drop the index and "re-create". So the real cost is basically nothing, once the index itself has been created.
Creating indexes
So since mongoose is just a layer on top of the standard API, the createIndex() method contains all the details of what is happening.
There are some details to consider here, such as that an index build can happen in the "background", and while this is less intrusive to your application it does come at it's own cost. Notably that the index size from "background" generation will be larger than if you built it n the foreground, blocking other operations.
Also all indexes come at a cost, notably in terms of disk usage as well as an additional cost of writing the additional information outside of the collection data itself.
The adavantages of an index are that it is much faster to "search" for values contained within an index than to seek through the whole collection and match the possible conditions.
These are the basic "trade-offs" associated with indexes.
Deployment Pattern
Back to the quoted block from the documentation, there is a real intent behind this advice.
It is typical in deployment patterns and particularly with data migrations to do things in this order:
Populate data to relevant collections/tables
Enable indexes on the collection/table data relevant to your needs
This is because there is a cost involved with index creation, and as mentioned earlier it is desirable to get the most optimum size from the index build, as well as avoid having each document insertion also having the overhead of writing an index entry when you are doing this "load" in bulk.
So that is what indexes are for, those are the costs and benefits and the message in the mongoose documentation is explained.
In general though, I suggest reading up on Database Indexes for what they are and what they do. Think of walking into a library to find a book. There is a card index there at the entrance. Do you walk around the library to find the book you want? Or do you look it up in the card index to find where it is? That index took someone time to create and also keep it updated, but it saves "you" the time of walking around the whole library just so you can find your book.

KeystoneJS relationship type, limit available items by field value

Is it possible to limit available displayed options in a relationship type of KeystoneJS by specifying a value condition?
Basically, a model has two sets of array fields, instead of letting the admin user select any item from the field, I would like to restrict to only the items that are part of a specific collection _id.
Not sure if this is exactly the feature you're looking for, but you can specify a filter option on the Relationship field as an object and it will filter results so only those that match are displayed.
Each property in the filter object should either be a value to match in the related schema, or it can be a dynamic value matching the value of another path in the schema (you prefix the path with a :).
For example:
User Schema
User.add({
state: { type: Types.Select, options: 'enabled, disabled' }
});
Post Schema
// Only allow enabled users to be selected as the author
Post.add({
author: { type: Types.Relationship, ref: 'User', filter: { state: 'enabled' } }
});
Or for a dynamic example, imagine you have a role setting for both Posts and Users. You only want to match authors who have the same role as the post.
User Schema
User.add({
userRole: { type: Types.Select, options: 'frontEnd, backEnd' }
});
Post Schema
Post.add({
postRole: { type: Types.Select, options: 'frontEnd, backEnd' },
// only allow users with the same role value as the post to be selected
author: { type: Types.Relationship, ref: 'User', filter: { userRole: ':postRole' } }
});
Note that this isn't actually implemented as back-end validation, it is just implemented in the Admin UI. So it's more of a usability enhancement than a restriction.
To expand on Jed's answer, I think the correct property (at least in the latest version of KeystoneJS 0.2.22) is 'filters' instead of 'filter'. 'filter' doesn't work for me.

Resources