Let's Go Further عملیات CRUD › حذف یک فیلم
قبلی · فهرست مطالب · بعدی
فصل 7.5.

حذف یک فیلم

در این فصل، آخرین نقطه پایانی CRUD را اضافه خواهیم کرد تا یک کلاینت بتواند یک فیلم مشخص را از سیستم ما حذف کند.

عملیات هندلر الگوی URL متد
نمایش اطلاعات برنامه healthcheckHandler /v1/healthcheck GET
ایجاد یک فیلم جدید createMovieHandler /v1/movies POST
نمایش جزئیات یک فیلم مشخص showMovieHandler /v1/movies/:id GET
به‌روزرسانی جزئیات یک فیلم مشخص updateMovieHandler /v1/movies/:id PUT
حذف یک فیلم مشخص deleteMovieHandler /v1/movies/:id DELETE

در مقایسه با سایر نقاط پایانی در API ما، رفتاری که می‌خواهیم در اینجا پیاده‌سازی کنیم کاملاً ساده است.

کوئری SQL برای حذف رکورد در پایگاه داده ما نیز ساده است:

DELETE FROM movies
WHERE id = $1

در اینجا کوئری SQL هیچ ردیفی برنمی‌گرداند، بنابراین استفاده از متد Exec() زبان Go برای اجرای آن مناسب است.

یکی از نکات خوب متد Exec() این است که یک مقدار sql.Result برمی‌گرداند که حاوی اطلاعاتی درباره تعداد ردیف‌هایی است که کوئری تحت تأثیر قرار داده است. در سناریوی ما، این اطلاعات واقعاً مفید است.

افزودن نقطه پایانی جدید

بیایید متد Delete() در مدل پایگاه داده خود را به‌روزرسانی کنیم. در اصل، می‌خواهیم این متد کوئری SQL بالا را اجرا کند و در صورتی که تعداد ردیف‌های تحت تأثیر 0 باشد، خطای ErrRecordNotFound برگرداند. به این صورت:

فایل: internal/data/movies.go
package data

...

func (m MovieModel) Delete(id int64) error {
    // Return an ErrRecordNotFound error if the movie ID is less than 1.
    if id < 1 {
        return ErrRecordNotFound
    }

    // Construct the SQL query to delete the record.
    query := `
        DELETE FROM movies
        WHERE id = $1`

    // Execute the SQL query using the Exec() method, passing in the id variable as
    // the value for the placeholder parameter. The Exec() method returns a sql.Result
    // value.
    result, err := m.DB.Exec(query, id)
    if err != nil {
        return err
    }

    // Call the RowsAffected() method on the sql.Result value to get the number of rows
    // affected by the query.
    rowsAffected, err := result.RowsAffected()
    if err != nil {
        return err
    }

    // If no rows were affected, we know that the movies table didn't contain a record
    // with the provided ID at the moment we tried to delete it. In that case we 
    // return an ErrRecordNotFound error.
    if rowsAffected == 0 {
        return ErrRecordNotFound
    }

    return nil
}

پس از اتمام این کار، بیایید به فایل cmd/api/movies.go برویم و یک متد جدید deleteMovieHandler اضافه کنیم. در این متد باید شناسه فیلم را از URL درخواست بخوانیم، متد Delete() که همین الان ساختیم را فراخوانی کنیم و — بر اساس مقدار برگشتی از Delete() — پاسخ مناسب را به کلاینت ارسال کنیم.

فایل: cmd/api/movies.go
package main

...

func (app *application) deleteMovieHandler(w http.ResponseWriter, r *http.Request) {
    // Extract the movie ID from the URL.
    id, err := app.readIDParam(r)
    if err != nil {
        app.notFoundResponse(w, r)
        return
    }

    // Delete the movie from the database, sending a 404 Not Found response to the
    // client if there isn't a matching record.
    err = app.models.Movies.Delete(id)
    if err != nil {
        switch {
        case errors.Is(err, data.ErrRecordNotFound):
            app.notFoundResponse(w, r)
        default:
            app.serverErrorResponse(w, r, err)
        }
        return
    }

    // Return a 200 OK status code along with a success message.
    err = app.writeJSON(w, http.StatusOK, envelope{"message": "movie successfully deleted"}, nil)
    if err != nil {
        app.serverErrorResponse(w, r, err)
    }
}

در نهایت، باید مسیر جدید را در فایل cmd/api/routes.go خود متصل کنیم:

فایل: cmd/api/routes.go
package main

...

func (app *application) routes() http.Handler {
    router := httprouter.New()

    router.NotFound = http.HandlerFunc(app.notFoundResponse)
    router.MethodNotAllowed = http.HandlerFunc(app.methodNotAllowedResponse)

    router.HandlerFunc(http.MethodGet, "/v1/healthcheck", app.healthcheckHandler)
    router.HandlerFunc(http.MethodPost, "/v1/movies", app.createMovieHandler)
    router.HandlerFunc(http.MethodGet, "/v1/movies/:id", app.showMovieHandler)
    router.HandlerFunc(http.MethodPut, "/v1/movies/:id", app.updateMovieHandler)
    // Add the route for the DELETE /v1/movies/:id endpoint.
    router.HandlerFunc(http.MethodDelete, "/v1/movies/:id", app.deleteMovieHandler)

    return app.recoverPanic(router)
}

بسیار خب، بیایید API را مجدداً راه‌اندازی کنیم و این را با حذف فیلم Deadpool از پایگاه داده فیلم‌هایمان آزمایش کنیم (اگر همراه ما بودید، این فیلم باید شناسه 3 داشته باشد). عملیات حذف باید بدون هیچ مشکلی کار کند و باید پیام تأیید را به این صورت دریافت کنید:

$ curl -X DELETE localhost:4000/v1/movies/3
{
    "message": "movie successfully deleted"
}

اگر درخواست حذف یکسانی برای فیلمی که قبلاً حذف شده تکرار کنید، اکنون باید یک پاسخ 404 Not Found و پیام خطا دریافت کنید. مشابه این:

$ curl -X DELETE localhost:4000/v1/movies/3
{
    "error": "the requested resource could not be found"
}