Coder Social home page Coder Social logo

bookshelf-cascade-delete's Introduction

bookshelf-cascade-delete

This Bookshelf.js plugin provides cascade delete with a simple configuration on your models.

Status

npm version build status coverage status

Installation

Install the package via npm:

$ npm install --save bookshelf-cascade-delete

Usage

Require and register the bookshelf-cascade-delete plugin:

var bookshelf = require('bookshelf')(knex);
var cascadeDelete = require('bookshelf-cascade-delete');

bookshelf.plugin(cascadeDelete);

Define which relations depend on your model when it's destroyed with the dependents prototype property:

var Post = bookshelf.Model.extend({
  tableName: 'Post'
});

var Author = bookshelf.Model.extend({
  tableName: 'Author',
  posts: function() {
    return this.hasMany(Post);
  }
}, {
  dependents: ['posts']
});

If you're using the ES6 class syntax, define dependents as static property:

class Author extends bookshelf.Model {
  get tableName() {
    return 'Author';
  }

  posts() {
    return this.hasMany(Post);
  }

  static dependents = ['posts'];
}

Use destroy to delete your model:

Author.forge({ id: 1 }).destroy();

A transaction is created and all the cascade queries executed:

DELETE FROM "Post" where "author_id" IN (1)
DELETE FROM "Author" where "id" IN (1)

You can pass an existing transaction as you would normally do:

bookshelf.transaction(function(transaction) {
  return Author.forge({ id: 1 }).destroy({ transacting: transaction })
}).then(function() {
  return Author.forge({ id: 2 }).destroy({ transacting: transaction })
});

It's possible to disable the cascade delete with the cascadeDelete option:

Author.forge({ id: 1 }).destroy({ cascadeDelete: false });

Since this plugin extends the destroy method, if you're extending or overriding it on your models make sure to call its prototype after your work is done:

var Author = bookshelf.Model.extend({
  tableName: 'Author',
  posts: function() {
    return this.hasMany(Post);
  },
  destroy: function() {
    // Do some stuff.
    sendDeleteAccountEmail(this);

    // Call the destroy prototype method.
    bookshelf.Model.prototype.destroy.apply(this, arguments);
  }
}, {
  dependents: ['posts']
});

Contributing

Contributions are welcome and greatly appreciated, so feel free to fork this repository and submit pull requests.

bookshelf-cascade-delete supports PostgreSQL and MySQL. You can find test suites for each of these database engines in the test/postgres and test/mysql folders.

Setting up

  • Fork and clone the bookshelf-cascade-delete repository.
  • Duplicate test/postgres/knexfile.js.dist and test/mysql/knexfile.js.dist files and update them to your needs.
  • Make sure all the tests pass:
$ npm test

Linting

bookshelf-cascade-delete enforces linting using ESLint with the Seegno-flavored ESLint config. We recommend you to install an eslint plugin in your editor of choice, although you can run the linter anytime with:

$ eslint src test

Pull Request

Please follow these advices to simplify the pull request workflow:

  • If you add or enhance functionality, an update of README.md usage section should be part of the PR.
  • If your PR fixes a bug you should include tests that at least fail before your code changes and pass after.
  • Keep your branch rebased and fix all conflicts before submitting.
  • Make sure Travis build status is ok.

Credits

This plugin's code is heavily inspired on the tkellen contribution for this issue, so cheers to him for making our job really easy!

License

MIT

bookshelf-cascade-delete's People

Contributors

abelsoares avatar kamronbatman avatar nunorafaelrocha avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

bookshelf-cascade-delete's Issues

Problem using registry plugin

Ho there, I am using bookshelf with registry plugin and I have the following model:
var bookshelf = require('../bin/database');
var cascadeDelete = require('bookshelf-cascade-delete');

bookshelf.plugin(cascadeDelete);
bookshelf.plugin('registry');

var Client = bookshelf.Model.extend({
tableName: 'clients',
hasTimestamps: true,

users: function() {
    return this.hasMany('User');
},

companies: function() {
    return this.hasMany('ClientsCompany');
},

jobs: function() {
    return this.hasMany('ClientsJob');
},

candidates: function() {
    return this.hasMany('Lead');
}

}, {
dependents: ['users' ]
});
module.exports = bookshelf.model('Client', Client);

I am trying to use cascade-delete but it gives me an error:
TypeError: Cannot read property 'column' of undefined
at Child.cascadeDelete (C:\Projects\copy\frozen-crawl\node_modules\bookshelf-cascade-delete\dist\index.js:95:54)

I guess it doesn't like the string in hasMany(). I have a ton of models using the registry plugin and I'll appreciate a way to make this work without having to modify all of them...
Thanks in advance...
EDIT: here is the route:
router.delete('/:id', function(req, res, next) {
Client
.forge({ id: req.body.id })
.destroy()
.then((deleted) => {
res.status(201).json({
message: 'Client deleted'
})
})
.catch(err => {
console.log(err);
res.status(500).json({
title: 'An error has occured',
error: err
})
});
})

Delete specific child of parent item

i am trying to achieve something like this and can not find any documentation in this plugin to achieve it

basically i want to only delete specific items of the parent model without deleting the model

ParentModel.fetch({withRelated: ['items]]}).find({itemId: 200}).destroy()

Gives a "TypeError: Cannot read property 'column' of undefined"

I'm not sure what exactly triggered this in one particular modal. I am also using bookshelf-paranoia which also overrides destroy.

I was able to fix it for my case by replacing this._knex by this.eloquent.knex in line 70.

- const id = this.get(this.idAttribute) || this._knex.column(this.idAttribute);
+ const id = this.get(this.idAttribute) || this.eloquent.knex.column(this.idAttribute);

Destroying with where clause

The next code throws error:

new User().where({ username: 'some username' }).destroy();

This happens because of the code in the 'recursiveDeletes' function:

var parentValue = typeof parent === 'number' || typeof parent === 'string' ? '\'' + parent + '\'' : parent.toString();

Because there is no id so the parent is undefined...

Does not account for non-default idAttribute name

The plugin throws an error:

Unhandled rejection TypeError: Cannot read property 'toString' of undefined
    at Function.recursiveDeletes (/home/project/node_modules/bookshelf-cascade-delete/dist/index.js:91:113)
    at cascadeDelete (/home/project/node_modules/bookshelf-cascade-delete/dist/index.js:42:54)

Probably because it doesn't account for non-default idAttribute values like 'post_id' instead of 'id'

Morph relations are not supported

It seems like the constructed delete queries only match the IDs, while with morph relations you'd have to check both the ID & type columns. This leads to unwanted deletes.

I realized that the hard way, maybe it would be worth documenting this limitation and/or skipping the data (like you seem to do with belongsToMany) until this type of relation is supported.

many to many, junction table.

Hi, first of all, thank you for this plugin!! It's awesome! ๐Ÿ”ฅ

There's only one thing that I didn't find how to do and that is: when I destroy a model, if that model has manyToMany relationships, how do I proceed? for instance:

const User = bookshelf.Model.extend({
    tableName: 'users',
    hasTimestamps: true,

    addresses: function() {
        return this.hasMany('Location', 'user_id');
    },

    services: function() {
        return this.belongsToMany('Tag'); //n:m <--- this one right here
    },

    projects: function() {
        return this.hasMany('Project', 'user_id');
    },

    reviews: function() {
        return this.hasMany('Review', 'user_id');
    }
},{
    dependents:['addresses', 'services', 'projects', 'reviews']
});

there's a tags_users table in my database used as a junction table, but whenever I try to destroy this model, I get the following error:

Unhandled rejection error: delete from "tags" where user_id IN ('5') - column "user_id" does not exist at Connection.parseE (/Users/cesar/dev/criarme/constroifacil/pinky/node_modules/pg/lib/connection.js:539:11) at Connection.parseMessage (/Users/cesar/dev/criarme/constroifacil/pinky/node_modules/pg/lib/connection.js:366:17) at Socket.<anonymous> (/Users/cesar/dev/criarme/constroifacil/pinky/node_modules/pg/lib/connection.js:105:22) at emitOne (events.js:77:13) at Socket.emit (events.js:169:7) at readableAddChunk (_stream_readable.js:146:16) at Socket.Readable.push (_stream_readable.js:110:10) at TCP.onread (net.js:529:20)

Any ideas? Thank you again! ( i'm not sure if this is the place to post such a doubt, but since I didn't find any better place, I just posted here :D )

Can't attach on destroy events of cascaded delete

PROBLEM:
Can't listen for any destroy events when cascade deleting.

USAGE EXAMPLE:
Delete an image from a gallery, where Gallery is a collection of gallery images where each it's model is morphed into an universal/reused File model that handles files connected with file-system.

Gallery model

module.exports = bookshelf.Model.extend({
  tableName: 'item_gallery',
  hasTimestamps: false,

  image () { return this.morphOne('File', 'imageable'); }
}, {
  dependents: ['image']
});

File model

module.exports = bookshelf.model('File', {
  tableName: 'file',
  hasTimestamps: false,

  destroy () {
    console.log('destroy');

    return bookshelf.Model.prototype.destroy.apply(this, arguments);

  },

  initialize () {
    this.on('destroying', () => console.log('destroying'));
    this.on('destroyed', () => console.log('destroyed'));
  }, {
};

EXPECTED:
Output any console.log.

ACTUAL:
When destroying none of the console.log get called.

NOTE:
Using built-in bookshelf plugin registry for registering relationship models.

Wondering, why for the cascading destroying we can't just get the dependents list and for each using model.related(<item_from_dependents>).destroy() or map over them incase of model to destroy them?

Cascade delete many to many relations

I'm trying to delete all objects in the list, but only the support table and the news table itself are deleted

bookshelf.model('News', {
    tableName: 'news',
    files(){
      return this.belongsToMany('Files').through('NewsFiles');
    },
  }, {
    dependents: ['files']
});

delete:

await models.news.where({ id }).destroy({ cascadeDelete: true });

the files entry is not destroyed.

Self relations

hello, what if we have a self relation? I'm getting Maximum call stack size exceeded in the following model:

const Organisation = bookshelf.Model.extend({
  tableName: 'organisations',

  hasTimestamps: true,

  users: function() {
    return this.hasMany(User, 'OrganisationId');
  },
  clientOfOrganisation: function(){
    return this.belongsTo(Organisation, 'clientOfOrganisationId')
  },
  organisations: function(){
    return this.hasMany(Organisation, 'clientOfOrganisationId'); 
  },
},{
  dependents: ['users', 'organisations'],

  byName: function(name, params = {}){
    return this.forge().query({where:{ name: name }}).fetch(params);
  }
})

I think that the error exists in the following block, where the dependency map is building:

    dependencyMap(skipDependents = false) {
      if (skipDependents || !this.dependents) {
        return;
      }

      return reduce(this.dependents, (result, dependent) => {
        const { relatedData } = this.prototype[dependent]();
        const skipDependents = relatedData.type === 'belongsToMany';

        return [
          ...result, {
          dependents: relatedData.target.dependencyMap(skipDependents),
          key: relatedData.key('foreignKey'),
          model: relatedData.target,
          skipDependents,
          tableName: skipDependents ? relatedData.joinTable() : relatedData.target.prototype.tableName
        }];
      }, []);
    },

P.S. Is there an ES6 class support?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.