Coder Social home page Coder Social logo

adonis-lucid-soft-deletes's Introduction

Adonis Lucid Soft Deletes

Works with AdonisJS v6

Docs for AdonisJS v5

npm-image license-image typescript-image

This addon adds the functionality to soft deletes Lucid Models through the deleted_at flag

Works with @adonisjs/lucid@^20.1.*

Sometimes use the deleted_at flag for soft deletes could be not good way. More about it

Introduction

Sometimes you may wish to "no-delete" a model from database. When models are soft deleted, they are not actually removed from your database. Instead, a deleted_at attribute is set on the model indicating the date and time at which the model was "deleted".

๐Ÿ‘‰ The SoftDeletes mixin will automatically add the deleted_at attribute as Luxon / DateTime instance.

Installation

Install it using npm, yarn or pnpm.

# npm
npm i adonis-lucid-soft-deletes

# yarn
yarn add adonis-lucid-soft-deletes

# pnpm
pnpm add adonis-lucid-soft-deletes

After install call configure:

node ace configure adonis-lucid-soft-deletes

Usage

Make sure to register the provider inside adonisrc.ts file.

providers: [
  // ...
  () => import('adonis-lucid-soft-deletes/provider'),
]

You should add the deleted_at column to your database tables for models with soft deletes.

// migrations/1234566666_users.ts
import { BaseSchema } from '@adonisjs/lucid/schema'

export default class Users extends BaseSchema {
  protected tableName = 'users'

  async up() {
    this.schema.createTable(this.tableName, (table) => {
      // ...
      table.timestamp('deleted_at').nullable()
    })
  }
  // ...
}

Applying Soft Deletes to a Model

import { compose } from '@adonisjs/core/helpers'
import { SoftDeletes } from 'adonis-lucid-soft-deletes'

export default class User extends compose(BaseModel, SoftDeletes) {
  // ...columns and props
}

Now, when you call the .delete() method on the model, the deleted_at (customDeletedAtColumn) column will be set to the current date and time. However, the model's database record will be left in the table.

import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'

export default class UsersController {
  /**
   * Delete user by id
   * DELETE /users/:id
   */
  async destroy({ params, response }: HttpContext) {
    const user = await User.findOrFail(params.id)
    await user.delete()
    
    return user // or response.noContent()
  }
}

๐Ÿ’ฅ Soft delete only works for model instances. await User.query().delete() as before will delete models from database

๐Ÿ‘‰ When querying a model that uses soft deletes, the soft deleted models will automatically be excluded from all query results.

To determine if a given model instance has been soft deleted, you may use the .trashed getter:

import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'

export default class UsersController {
  /**
   * Get user by id
   * GET /users/:id
   */
  async show({ params }: HttpContext) {
    const user = await User.withTrashed().where('id', params.id).firstOrFail()
    if (user.trashed) {
      return response.forbidden()
    }
    return user
  }
}

Set custom column name for deletedAt

import { compose } from '@adonisjs/core/helpers'
import { SoftDeletes } from 'adonis-lucid-soft-deletes'

export default class User extends compose(BaseModel, SoftDeletes) {
  // ...columns and props

  @column.dateTime({ columnName: 'customDeletedAtColumn' })
  declare deletedAt: DateTime | null
}

Restoring Soft Deleted Models

To restore a soft deleted model, you may call the .restore() method on a model instance. Also, method .restore() exists after methods .withTrashed() and .onlyTrashed() The restore method will set the model's deleted_at column to null:

import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'

export default class TrashUsersController {
  /**
   * Update trashed user by id
   * PUT /trash/users/:id
   */
  async update({ params }: HttpContext) {
    const user = await User.withTrashed().where('id', params.id).firstOrFail()
    await user.restore()
    
    return user
    
    // or

    await User.withTrashed().where('id', params.id).restore()
    await User.query().withTrashed().where('id', params.id).restore()
  }
}

Permanently Deleting Models

Sometimes you may need to truly remove a model from your database. You may use the .forceDelete() method to permanently remove a soft deleted model from the database table:

import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'

export default class UsersController {
  /**
   * Delete user by id
   * DELETE /users/:id
   */
  async destroy({ params, response }: HttpContext) {
    const user = await User.findOrFail(params.id)
    await user.forceDelete()
    
    return response.noContent()
  }
}

Including Soft Deleted Models

As noted above, soft deleted models will automatically be excluded from query results. However, you may force soft deleted models to be included in a query's results by calling the .withTrashed() method on the model:

import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'

export default class UsersController {
  /**
   * Get a list users
   * GET /users?withTrashed=1
   */
  async index({ request }: HttpContext) {
    const usersQuery = request.input('withTrashed')
      ? User.withTrashed()
      : User.query()

    return usersQuery.exec()

    // or

    return User.query().if(request.input('withTrashed'), (query) => {
      query.withTrashed()
    }).exec()
  }
}

Retrieving only Soft Deleted Models

The .onlyTrashed() method will retrieve only soft deleted models:

import type { HttpContext } from '@adonisjs/core/http'
import User from '#models/user'

export default class TrashUsersController {
  /**
   * Get a list trashed users
   * GET /trash/users
   */
  async index({ request }: HttpContext) {
    return User.onlyTrashed().exec()
  }
}

Soft Deletes methods

Methods .withTrashed(), .onlyTrashed() and .restore() also available in ModelQueryBuilder for models with soft delete, example:

await User.query().withTrashed().exec()
await User.query().onlyTrashed().restore()

adonis-lucid-soft-deletes's People

Contributors

lookingit avatar nurycaroline 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

adonis-lucid-soft-deletes's Issues

TypeError: this.model.query.$getColumn is not a function

TypeError: this.model.query.$getColumn is not a function at ModelQueryBuilder.onlyTrashed (adonis-lucid-soft-deletes/build/src/Bindings/ModelQueryBuilder.js:36:50)

The problem is in this two lines:

const deletedAtColumn = this.model.query.$getColumn('deletedAt')?.columnName

const deletedAtColumn = this.model.query.$getColumn('deletedAt')?.columnName

this.model is LucidModel and query is a function, as well as $getColumn is method of LucidModel but not ModelQueryBuilderContract. So it should be this.model.$getColumn().

Prerequisites

  • "@adonisjs/lucid": "18.0.0"
  • "adonis-lucid-soft-deletes": "1.4.2"

ER_BAD_FIELD_ERROR

Hello, thanks for this great package. I've implemented according to your readme, and unfortunately got the below error, when trying to delete a user.

"update `users` set `updated_at` = '2021-08-19 09:27:52', `deleted_at` = 2021-08-19 09:27:52.239 +00:00 where `id` = 8 - ER_BAD_FIELD_ERROR: Unknown column '_zone' in 'field list'"

Could you please help me out with this? Thanks in advance!

My package.json:

{
  "name": "my-backend",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "build": "node ace build --production",
    "start": "node server.js",
    "dev": "node ace serve --watch",
    "lint": "eslint . --ext=.ts",
    "format": "prettier --write ."
  },
  "devDependencies": {
    "@adonisjs/assembler": "^5.3.2",
    "adonis-preset-ts": "^2.1.0",
    "eslint": "^7.30.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-adonis": "^1.3.2",
    "eslint-plugin-prettier": "^3.4.0",
    "pino-pretty": "^5.1.1",
    "prettier": "^2.3.2",
    "typescript": "^4.2.4",
    "youch": "^2.2.2",
    "youch-terminal": "^1.1.1"
  },
  "dependencies": {
    "@adonisjs/core": "^5.1.9",
    "@adonisjs/lucid": "^15.0.3",
    "@adonisjs/repl": "^3.1.4",
    "adonis-lucid-filter": "^4.0.2",
    "adonis-lucid-soft-deletes": "^1.2.0",
    "luxon": "^2.0.1",
    "mysql": "^2.18.1",
    "phc-argon2": "^1.1.2",
    "proxy-addr": "^2.0.7",
    "reflect-metadata": "^0.1.13",
    "source-map-support": "^0.5.19"
  }
}

AdonisJS app is running in docker, if that matters.

Breaks where in related query

Hi,

Recently I updated Adonis, Lucid, and soft-deletes to their latest versions, and now where query in preload is broken.

Following are the version updates:

@adonisjs/core ^5.5.3 โ†’ ^5.8.3
@adonisjs/lucid ^17.1.0 โ†’ ^18.0.0
adonis-lucid-soft-deletes ^1.3.3 โ†’ ^1.4.1

I get the "Cannot read properties of undefined (reading 'method')" error.

So I inspected to find the error and seems like the issue is caused by the toQuery() method on line 32 of the following file:
https://github.com/lookinlab/adonis-lucid-soft-deletes/blob/develop/src/SoftDeletes/index.ts

Replacing const isGroupLimitQuery = query.toQuery().includes('adonis_group_limit_counter') with
let sqlQuery = query.toSQL().toNative()
const isGroupLimitQuery = sqlQuery.sql.includes('adonis_group_limit_counter'); seem to fix the issue.

I can create a PR if that helps.

Thanks

total in paginate is false

Hello,

When using the paginate function, the total counts the deleted record. So, in a database of 3 users with one deleted. Total will be 3 instead of 2 !

To fixed, we need to refer to the documentation

A pull request is ready to fix this issue (will be created after you accept #2) and a reproduction repository can be found here

Thanks for you works!

ERROR

A 'deletedAt' property on type 'User' cannot be assigned to the same property on base type 'LucidRow & { $forceDelete: boolean; DeletedIn: DateTime | null; read-only discarded: boolean; $getQueryFor(action: "insert", client: QueryClientContract): InsertQueryBuilderContract<...>; $getQueryFor(action: "update" | ... 1 more ... | "update", client: QueryClientContract): ModelQueryBuilderContract<...>; delete(): ...'.

GroupLimit create an error

Hello,

Using Soft deletes on a preloaded model with group limit will create an error :

 const associations = await Association.query()
      .preload('tags', (tags) => tags.groupLimit(3))
      .paginate(page, this.PER_PAGE)

There is an error because the no retrieval of deleted items clause is added at the end of the query.

image

soft deleting many to many pivot records?

Hi,

Is it currently possible to soft delete pivot records?

Imagine I have a relationship like the following:

users

  • id
  • deleted_at

(many to many)
user_addresses

  • id
  • user_id fk
  • address_id fk
  • deleted_at <-- i want this

addresses

  • id
  • deleted_at

The library does work well when addresses are soft deleted, despite the pivot relationship still being there (user.addresses relationship is empty).

However, I would also like the pivot table record to be soft deleted as well (when calling the likes of detach). Is that possible out of the box with this package? I couldn't find any references in the codebase for pivot or many to many that looked like they handle the automatic pivot model that adonis creates

Contributing Guide

Hello,

Can you provide a contributing guide please?

I think I found an issue and I want to verify it !

Thanks for this module !

Rename deleted_at to deletedAt

Hi, in my project, all models uses camel case naming strategy by default, and i need to rename deleted_at to deletedAt column, how can i rename this column in my model?

My migration file:
image

My model file (rename column via columnName property in decorator not helped and worked):
image

Error:
image

Thanks!

`isDeleted` is false after calling `delete` on a model using SoftDeletes

Steps to reproduce:

  • create a model that uses the soft deletes mixin
  • a test case with the following:
const user = await UserFactory.create();
await user.delete();

assert.isTrue(!!user.deletedAt);
assert.isTrue(user.$isDeleted);

Expected Output:

  • test passes

Actual Output:

  • Assertion Error: expected false to be true on the assert.isTrue(user.$isDeleted); line.

Is there another expected way to check if a model is "soft deleted", or would you consider this a bug? Considering that this package replaces delete functionality entirely, I would personally expect $isDeleted to be true.

select * from "model_with_soft_deletes" where "id" = $1 and "model_with_soft_deletes"."deleted_at" is null limit $2 - relation "model_with_soft_deletes" does not exist

โฏ migrated database/migrations/1698706721243_user_soft_deletes

Migrated in 1.38 s
โฏ error     database/seeders/development/0_User
  select * from "model_with_soft_deletes" where "email" in ($1, $2, $3, $4, $5) and "model_with_soft_deletes"."deleted_at" is null for update - relation "model_with_soft_deletes" does not exist

Was I supposed to make a model for this? If so, that is not explained in your docs and the code is so heavily abstracted I can't make heads or tails of where "model_with_soft_deletes" is even coming from.

I'm also getting the same error as 16.

 // set custom `deletedAt` column name
  @column.dateTime({ columnName: 'customDeletedAtColumn' })
  public deletedAt?: DateTime | null

public deletedAt?: DateTime | null produces the following TypeScript error:

Property 'deletedAt' in type 'User' is not assignable to the same property in base type 'LucidRow & { $forceDelete: boolean; deletedAt: DateTime | null; readonly trashed: boolean; $getQueryFor(action: "insert", client: QueryClientContract): InsertQueryBuilderContract<...>; $getQueryFor(action: "update" | ... 1 more ... | "refresh", client: QueryClientContract): ModelQueryBuilderContract<...>; delete(): ...'.
  Type 'DateTime | null | undefined' is not assignable to type 'DateTime | null'.
    Type 'undefined' is not assignable to type 'DateTime | null'.ts(2416)
(property) User.deletedAt?: DateTime | null | undefined

Bulk delete doesn't work

Hello, this case will delete row instead of setting deleted_at

await Categories
  .query()
  .whereIn('id', ids)
  .delete()

Issue with `useTransaction()`. Returning any after upgrading to AdonisJS V6

After upgrading my application to AdonisJS V6, I've encountered an issue with the useTransaction() method's type definition. In version 5, calling this method preserved the instance type, enabling IntelliSense for subsequent method calls. However, in version 6, useTransaction() returns a type of any, which disrupts IntelliSense and affects code clarity.

Code Example:

const product = await new Product()
  .fill({ /* property values */ })
  .useTransaction(trx) // Now returns `any`, affecting IntelliSense
  .save();

// Here, "product" is inferred as `any`, losing type specificity

Expected Behavior:

In AdonisJS V5, useTransaction() would return the specific type of the instance (e.g., this), allowing for continued IntelliSense support and type safety in chained method calls.

Actual Behavior:

In AdonisJS V6, useTransaction() returns any, leading to a loss of IntelliSense and type safety for any subsequent chained method calls.

Additional Information:

Navigating to the definition of useTransaction() takes me to mixin.d.ts from SoftDeletes, unlike in V5, where it would lead to the LucidRow interface definition in AdonisJS.

Request:

Could the type definition for useTransaction() be adjusted to maintain the instance's type, as in V5, to support IntelliSense and enhance developer experience? Any guidance or workarounds would also be greatly appreciated.

Repository:

I created this repository with the issue: https://github.com/GustavoSabel/issue-soft-deletes-adonisjsv6

Wrong interfaces for ignoreDeleted and ignoreDeletedPaginate methods

These interfaces describe both methods as if they had no parameters:

type ModelWithSoftDeletes = LucidModel & {
ignoreDeleted(): void
ignoreDeletedPaginate(): void

export interface SoftDeletesMixin {
<T extends NormalizeConstructor<LucidModel>>(superclass: T): T & {
ignoreDeleted(): void,
ignoreDeletedPaginate(): void

But they actually have one:

public static ignoreDeleted<Model extends typeof ModelWithSoftDeletes>(
query: ModelQueryBuilderContract<Model>
): void {

public static ignoreDeletedPaginate ([countQuery, query]): void {

Prerequisites

  • "@adonisjs/lucid": "18.0.0"
  • "adonis-lucid-soft-deletes": "1.4.3"

Add deleted_at in where to link tables

Hi
When using preload, I think that deleted_at should be added to all tables involved.
For example, if a link between book and author is removed, the search will still bring the link, because the deleted_at is for the Book table, I think I should add it to the author_book table as well.

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.