Overriding Backbone.Collection.add to enforce uniqueness on non-id property I've overridden Backbone.Collection.add for my Collection to ensure that the Collection never has two PlaylistItems who share a Song ID. My implementation seems incredibly verbose and I was curious if there's a better way to express my desires:

var PlaylistItems = MultiSelectCollection.extend(_.extend({}, SequencedCollectionMixin, {
    model: PlaylistItem,

    //  Don't allow duplicate PlaylistItems, determined by the PlaylistItem's Song's ID. 
    add: function (items, options) {

        if (items instanceof Backbone.Collection) {
            items.each(function (item) {
                trySetDuplicateSongId.call(this, item);
            }.bind(this));
        }
        else if (_.isArray(items)) {
            _.each(items, function (item) {
                trySetDuplicateSongId.call(this, item);
            }.bind(this));
        } else {
            trySetDuplicateSongId.call(this, items);
        }

        return MultiSelectCollection.prototype.add.call(this, items, options);
    }

}));

function trySetDuplicateSongId(playlistItemToAdd) {
    var duplicatePlaylistItem = this.find(function (playlistItem) {
        return playlistItem.get('song').get('id') === playlistItemToAdd.get('song').get('id');
    });

    var duplicateFound = !_.isUndefined(duplicatePlaylistItem);

    if (duplicateFound) {

        //  Make their IDs match to prevent adding to the collection.
        if (duplicatePlaylistItem.has('id')) {
            playlistItemToAdd.set('id', duplicatePlaylistItem.get('id'));
        } else {
            playlistItemToAdd.cid = duplicatePlaylistItem.cid;
        }

    }
}
I've been looking at this since a while, it seems mostly verbose because you want to handle Backbone collections, and arrays, and single songs..

It seems simpler to normalize in all 3 scenario's items to something that you then process once. I am not 100% this works, but you should get the gist:

//  Don't allow duplicate PlaylistItems, determined by the PlaylistItem's Song's ID. 
add: function (items, options) {

   if( !items.length ){ 
     //Anti duck typing, it's not an array or collection
     items = [items];
   }
   else if ( items instanceof Backbone.Collection ){
     //Provide direct access to the array
     items = items.models;
   }
   _.each(items, function (item) { 
       trySetDuplicateSongId.call(this, item);
   }.bind(this));

    return MultiSelectCollection.prototype.add.call(this, items, options);
}


For trySetDuplicateSongId I would extract playlistItemToAdd.get('song').get('id') and cache it into a variable. This should make your search twice as fast. Also I would clean up the whole duplication finding, I find the code a bit verbose. Again, I am making some assumptions here with regards to what you set as defaults, but I would do this:

function trySetDuplicateSongId(playlistItemToAdd) {

    var songId = playlistItemToAdd.get('song').get('id'),
        duplicate = this.find(function (playlistItem) {
            return playlistItem.get('song').get('id') === songId;
        });

    if (duplicate) {
       //  Make their IDs match to prevent adding to the collection.
        if (duplicatePlaylistItem.has('id')) {
            playlistItemToAdd.set('id', duplicatePlaylistItem.get('id'));
        } else {
            playlistItemToAdd.cid = duplicatePlaylistItem.cid;
        }
    }
}


Furthermore, if you have to add lots of songs, then you will run into increasingly bad performance. I would probably have a separate list/object with just the song id's pointing to an object with item id or cid.

Finally, I am curious as to why you would not allow a playlist item to be shared by song lists ;P