Force pair of attributes to be unique - node.js

I have a model which contains relationships between a user and his contacts.
Here is the current code for this model :
module.exports = {
connection: 'mysqlServer',
attributes: {
user: {
model: 'user',
required: true
},
contact: {
model: 'user',
required: true
}
}
};
What I would like is to make the combo user and contact uniques. That means that there may be several identical users and several identical contacts but only one user with a specific contact (ie: we can have user=1, contact=1 and user=1, contact=2, but we can't have user=1, contact=1 and user=1, contact=1).
The unique validation property is not enough to create the validation I want.
Do you have an idea how I should proceed? With custom validation rules maybe?

Related

Duplicate null key value error MongoDB with Mongoose

I'm trying to add a user to my users collection and keep getting a duplicate null key value error.
My Users model used to look like this like this:
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
profilePictures: [{
link: {
type: String
}
rank: {
type: Number,
unique: true
}
}],
});
module.exports = User = mongoose.model("users", UserSchema);
Before I changed the pictures field to
...
pictures = []
...
I believe because I saved users under the former schema, it has saved somewhere the model of the object in the pictures array (they would be given an ObjectId when I saved something to that array).
Even though I have changed the field to
pictures = []
I still get this error
E11000 duplicate key error collection: testDB.users index: profilePictures.rank_1 dup key: { profilePictures.rank: null }
When neither profilePictures nor rank fields even exist anymore.
I imagine I can probably just delete the users collection and start again but I want to know if there is a better way to handle this error? Suppose I had 100 users in the users collection – I wouldn't be able to just delete them all.
Thanks!
you added unique property true in your model to profilePictures.rank. on first request it is saving null as you may be not providing rank in your query.
second time it is again trying to save null but it is also marked unique so it is throwing exception.
solution:
remove unique flag from profilePictures.rank
provide unique value for profilePictures.rank

Node/Express Project: Create and Sync a "Join Class/Model" to a Postgres Database

I'm building a Node/Express/Postgres version of an app that I already built in Rails. I'm learning Node, so I figured I'd rebuild something that I know works.
For now, I'm dumping everything in one file (set up my database, defined my models, etc.), just to make sure I have everything set up correctly before I divvy them up into different files.
I set up my postgres database at the very top of the file, like so:
var Sequelize = require('sequelize');
var db = new Sequelize('my_database_name', 'my_username', null, {
host: 'localhost',
dialect: 'postgres',
});
With regard to my models, I have a Politician model:
var Politician = db.define("politician", {
name: {
type: Sequelize.STRING,
},
politicalParty: {
type: Sequelize.STRING
}
});
A Category model:
var Category = db.define("category", {
name: {
type: Sequelize.STRING
},
keywords: {
type: Sequelize.ARRAY(Sequelize.TEXT)
},
});
And a join model of Politician and Category, called "Interest". Because Interest is a join model, it will have a "politicianId" and "categoryId" properties....but will those properties automatically generate in the database? And so, is this how I would define the Interest model, with no properties?
Interest Model:
var Interest = db.define("interest")
Or, will I have to be specific, and create "politicianId" and "categoryId" properties? Like so:
Interest Model:
var Interest = db.define("interest", {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
categoryId: {
type: Sequelize.INTEGER,
foreignKey: true
},
politicianId: {
type: Sequelize.INTEGER,
foreignKey: true
}
});
Also, do I need the "foreignKey: true" bit? Or will it automatically know that those properties are foreign keys? Also, do I need the "id" property? I know models automatically create their own primary key "id"...but again, I've been at this for hours, looking at docs, and trying everything.
I then defined my associations (again, all of this is the same file):
Politician.belongsToMany(Category, {through: "Interest"});
Category.belongsToMany(Politician, {through: "Interest"});
The Node/Sequelize docs seems to suggest that defining those 2 associations above will automatically "create a new model called Interest with the equivalent foreign keys politicianId and categoryId." So, do I even need to define a "Interest" model? Also, do I need the follow associations to describe that Interest belongs to Politician and Category?
Interest.belongsTo(Politician);
Interest.belongsTo(Category);
If I don't write the associations saying that Interest belongs to Politican and Catetory, I don't get the "politicianId" and "categoryId" columns in the Interest table. Just the "id" and createdAt/updatedAt columns.
I then created an instance of Politician, Category, and Interest, to persist everything to the database, to see if everything is there and set up correctly:
Politician Object:
var politician1 = Politician.sync({force: true}).then(function(){
return Politician.create(aPoliticianObjectDefinedInthisFile);
});
This works perfectly. I see this object in the politician table in the database.
Category Object:
var category1 = Category.sync({force: true}).then(function(){
return Category.create(aCategoryObjectDefinedInThisFile);
});
This works perfectly. I see this object in the category table in the database.
Here is what doesn't work. Creating an instance/object of Interest and synching it to the database. My thinking is, if I put integers as values, it will know that "politicianId: 1" means point to the politician object with an id of 1, and the same for "categoryId: 1". But when I write it as I have it below, the Interest table doesn't even show up in the Postgres database at all.
Interest Object:
Interest.sync({force: true}).then(function(){
return Interest.create(
{
politicianId: 1,
categoryId: 1
}
);
});
However, when I create the object of Interest like this, with no properties defined, the Interest table appears in the database, along with the "politicianId" and "categoryId" columns, however, those columns are empty. The object's primary id is in there at 1, and the "createdAt" and "updatedAt" columns have data too. But the foreign key columns are blank.
Interest Object:
Interest.sync({force: true}).then(function()
{
return Interest.create(
{
// No properties defined.
}
);
}
);
Sorry for this long post, lol, but, in all:
Am I creating the "Interest" model correctly?
Am I writing the associations for "Interest" correctly?
Do I even need to write associations for Interest, if I already have associations for its parent classes, Politican and Category defined?
In my Rails app, my associations for Politican and Category are like so:
Politician has_many interests
Politican has_many categories through interests
Category has_many interests
Category has_many politicians through interests
Interest belongs_to politician
Interest belongs_to category
But I use the "belongsToMay" association in Node because I got an error telling me to do so.
Basically, I need to create an instance of Politician, an instance of Category, and an instance of Interest that has "politicianId" and "categoryId" columns that point to those aforementioned instances of those classes.
politicanABC -- id: 1
categoryABC -- id: 1
instanceABC -- id: 1; politicanId: 1 (referring to politicanABC); categoryid: 1 (referring to categoryABC).
My app is set up like that in Rails and works wonderfully.
Help and thank you in advance :-)
You don't have to define the Interest model if you are not going to add any additional fields. Sequelize will internally define the model and add all required fields once you do following:
Politician.belongsToMany(Category, {through: "Interest"});
Category.belongsToMany(Politician, {through: "Interest"});
Sync needs to run on database level and not on tables since Interest model is implicit at this point.
db.sync({force: true});
Sequelize will add relationship build methods on both Politician and Category instances. Category will have methods addPolitician(), addPoliticians([]), setPoliticians([]), getPliticians(). Politician instances will have similar functions to associate categories to them. You can connect these after create option is performed on both objects successfully.
Politician.create({name: 'John Doe', politicalParty: 'Nice Party'})
.then(function(politician) {
Category.create({name: 'Nicers'})
.then(function(category) {
politician.addCategory(category);
});
});
You can also search and associate existing items using helper methods. Alternatively you can associate objects manually by accessing db.models.Interest model and running creates on it.

I am confused with Sails.js waterline one-to-one association logic

So the reason why im confused, because I am a PHP developer and used Laravel and FuelPHP alot
What i dont really understand is the association it self.
What i mean, i wanted to create a basic hasOne / BelongsTo logic, with the following
User has one profile
Profile belongs to an user
I am used to the following build up (Laravel style)
Users table
id | username | email | password
---------------------------------------
1 | My Username | My email | 1234568
Users_profile table
user_id | first_name | last_name
----------------------------------------
1 | My First name | My Last name
Then i just defined models this way
User model
class Users extends Eloquent
{
public function profile()
{
return $this->hasOne('profile');
}
}
Profile model
class Profile extends Eloquent
{
protected $tableName = 'users_profile';
protected $primaryKey = 'user_id';
public function user()
{
return $this->belongsTo('User');
}
}
And it just works, because the return $this->hasOne('profile'); will auto check for the user_id
Tried the same in Sails.js (in the sails way)
User model
module.exports = {
attributes: {
username: {
type: 'string',
unique: true,
required: true
},
email: {
type: 'string',
unique: true,
email: true,
required: true
},
password: {
type: 'string',
required: true
},
profile: {
model: "profile",
}
},
};
Profile model
module.exports = {
tableName: 'user_profile',
autoPK: false,
autoCreatedAt: false,
autoUpdateddAt: false,
attributes: {
user_id: {
type: 'integer',
primaryKey: true
},
first_name: {
type: 'string',
},
last_name: {
type: 'string',
},
user: {
model: "user"
}
}
};
And reading from the documentation now i have to update my table this way
id | username | email | password | profile
-------------------------------------------------
1 | My Username | My email | 1234568 | 1
user_id | first_name | last_name | user |
-----------------------------------------------
1 | My First name | My Last name | 1
So i need to store 2 more id's again, and i do not really get why.
Than i read further tried to use via did not work (noted that is for collections)
So, anybody could give me a logic example for a Laravelis style?
Foud nothing about this in the docs (a more easier way), because in my opinion if the user will have more relations, this will cause and ID hell (just my aopinion)
It is a known issue that Sails doesn't fully support one-to-one associations; you have to set the foreign key on whichever side you want to be able to populate from. That is, if you want to have User #1 linked to Profile #1 and be able to do User.find(1).populate('profile'), you would set the profile attribute of User #1, but that doesn't automatically mean that doing Profile.find(1).populate('user') will work. This is as opposed to many-to-many relationships in Sails, where adding the link on one side is sufficient. That's because to-many relationships use a join table, whereas to-one relationships do not.
The reason this hasn't been a priority in Sails is that one-to-one relationships are usually not really useful. Unless you have a really compelling reason for not doing so, you're better off just merging the two models into one.
In any case, if it's something you really need, you can use the .afterCreate lifecycle callback to ensure a bi-directional link, for example in User.js:
module.exports = {
attributes: {...},
afterCreate: function(values, cb) {
// If a profile ID was specified, or a profile was created
// along with the user...
if (values.profile) {
// Update the profile in question with the user's ID
return Profile.update({id: values.profile}, {user: values.id}).exec(cb);
}
// Otherwise just return
return cb();
}
};
You could add a similar .afterCreate() to Profile.js to handle updating the affected user when a profile was created.

Mongoose schema for arrays of refs with role

I'm very new to the NoSQL way of doing things so please excuse me if I'm just thinking about this completely wrong (I feel like I am).
My application has Users and Organizations, and the Users must have and belong to those Organizations with a role of member or owner.
Currently in my schema for my Users I have:
orgs: [{ type: Schema.Types.ObjectId, ref: 'Org' }]
and in my schema for my Organizations I have:
members: [{ type: Schema.Types.ObjectId, ref: 'User' }]
but I would also like to attach a role of member or owner to this.
Is this something I should put in the actual reference somehow like:
members: [{ type: Schema.Types.ObjectId, ref: 'User', role: String }]
or should the role be elsewhere in the schema? What is the proper way to handle this or is there a different way to schematize this that is more appropriate?
And once I have the schema set up for this properly, it would be helpful to have an example of how to create a few users/orgs with this roled ref.
You can do this with a small change to what you have there. I store messages in an app the same way (text and sender).
Here's what you could do:
members: [{
role: {
type: String
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
}],
Then when you want to add a member or members to an organization (assume you have already created an organization and a user separately):
function addNewMember(org_id, user_id, role) {
var member = {role: role, user: user_id};
Organization.findByIdAndUpdate(org_id, {"$push": {"members":member}}, function(err,org) {
...
});)
// you can use the $each clause to add a whole array of members at the same time too
// like so:
// {"$push": {"members":
// {
// "$each": [memberarray]
// }}}
}
And when you want to find an organization including it's members, you could do:
Organization.findById(org_id)
.populate('members.user')
.exec(callbackfunction);
Think carefully if you do absolutely need to duplicate the Organization -> member relationship for each member too as Member -> organizations. If this is indeed important, after successfully adding the user to the organization, in the callback you can do a second query to update the user's orgs field just like above.

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