Can we make a Relationship Field unique?

320 views
Skip to first unread message

Dgits

unread,
Jan 4, 2015, 12:49:25 AM1/4/15
to keyst...@googlegroups.com
How might one apply a uniqueness constraint against a KeystoneJS Type.Relationship?  The following Country.js model wants to disallow a User from serving as Prime Minister for more than one country in the same KeystoneJS instance but does not work as hoped.  Instead it lets Admin add herself to it as Prime Minister of both Australia and Japan:

var keystone = require('keystone'),
 
Types = keystone.Field.Types;

var Country = new keystone.List('Country');

Country.add({
  name
: { type: String, required: true, initial:true, index: true },
  primeMinister
: { type: Types.Relationship, ref: 'User', required: true, index: {unique: true}, initial: true },
});

Country.defaultColumns = 'name, primeMinister';
Country.register();


Thanks in advance for any assistance.

atlb...@gmail.com

unread,
Jan 4, 2015, 1:47:32 AM1/4/15
to keyst...@googlegroups.com
Change `index` to `true` and add `unique`
Country.add({
  name
: { type: String, required: true, initial:true, index: true },

  primeMinister
: { type: Types.Relationship, ref: 'User', required: true, index: true, unique: true, initial: true },
});


Dgits

unread,
Jan 4, 2015, 12:02:29 PM1/4/15
to keyst...@googlegroups.com
Thanks for the suggestion but changing model's primeMinister field definition to use index:true, unique: true has same result, i.e., Admin can still add itself as primeMinister to many records.  Could this issue be related to Issue #743? Thanks again for any help.

var keystone = require('keystone'),
 
Types = keystone.Field.Types;


var Country = new keystone.List('Country');


Country.add({
  name
: { type: String, required: true, initial:true, index: true },
  primeMinister
: { type: Types.Relationship, ref: 'User', required: true, index: true, unique: true, initial: true },
});


Dgits

unread,
Jan 4, 2015, 3:00:37 PM1/4/15
to keyst...@googlegroups.com
Looks like a pre hook may serve as a workaround.  Version of the model below seems to meet the need for now, please follow-up here if someone knows of a better way and thanks again.

var keystone = require('keystone'),
 
Types = keystone.Field.Types;

var Country = new keystone.List('Country');

Country.add({
  name
: { type: String, required: true, initial:true, index: true },

  primeMinister
: { type: Types.Relationship, ref: 'User', required: true, index: true, initial: true },

});

Country.defaultColumns = 'name, primeMinister';

Country.schema.pre('save', function(next) {

 
Country.model.find()
   
.where('primeMinister', this.primeMinister)
   
.exec()
   
.then(function (dupe) {
     
if(dupe && dupe[0]) {
       
next(new Error('User is already the Prime Minister of ' + dupe[0].name));
     
}
     
else {
       
next();
     
}
   
}, function (err) {
     
next(err);
   
});

});

Country.register();



Dgits

unread,
Jan 5, 2015, 9:57:05 PM1/5/15
to keyst...@googlegroups.com
Modified workaround lets the save proceed if it is against the original record but still prevents duplicate relationships from being stored:

var keystone = require('keystone'),
 
Types = keystone.Field.Types;

var Country = new keystone.List('Country');

Country.add({
  name
: { type: String, required: true, initial:true, index: true },
  primeMinister
: { type: Types.Relationship, ref: 'User', required: true, index: true, initial: true },
});

Country.defaultColumns = 'name, primeMinister';

Country.schema.pre('save', function(next) {


 
var detectDupe = function (err, dupe) {
   
if (err)
     
next(err);
   
if( (dupe && dupe[0]) && (this.id !== dupe[0].id) ) {

     
next(new Error('User is already the Prime Minister of ' + dupe[0].name));
   
}
   
else {
     
next();
   
}
 
}


 
var preventDupe = detectDupe.bind(this);


 
Country.model.find()
   
.where('primeMinister', this.primeMinister)

   
.exec(preventDupe);

});

Country.register();

atlb...@gmail.com

unread,
Jan 5, 2015, 11:04:46 PM1/5/15
to keyst...@googlegroups.com
I fired up a your model adding 
unique: true,
and got the same results.

So I added this in
Country.schema.pre('save',function(next) {
this.primeMinister2 = '54997a0dc474c1ef1b126414';
next();
});

Country.schema.add({primeMinister2: { type: keystone.mongoose.Schema.Types.ObjectId, unique:true, index: true, initial: true }});

And got the desired results when the second item was added.

So I believe you found a bug in the initial item creation.  It may well be associated with the Issue you referenced.  I will file a new issue to get this looked at.



Reply all
Reply to author
Forward
0 new messages