Soft Delete

저자가 잘 알고 지내는 IT 개발업체 대표가 있다. 그는 항상 애플리케이션과 데이터 중에서 둘 다 중요하지만, 특히 데이터는 한번 삭제되면 복구할 수 없고 실수로 삭제할 경우는 더욱 난감해 지기 때문에 백업의 중요성과 함께 애플리케이션에서의 모든 삭제는 soft delete 하는 것을 원칙으로 한다고 늘 말한다.

저자도 2년전에 작은 웹애플리케이션을 개발하여 관리하던 중 실수로 데이터베이스의 특정 테이블 데이터를 삭제한 것은 뒤늦게 알게 되어 곤욕을 치른 적이 있다.

저자가 생각하는 soft delete는 애플리케이션에서 행하는 모든 삭제는 데이터베이스에 delete 커밋(hard delete)하지 않고 삭제표시를 위한 플래그 속성을 추가해서 표시하므로써 select 문에서 삭제표시된 레코드를 제외하여 보여주는 것으로 대신하는 것이다.

이와 같이 soft delete는 hard delete에 비해 복구가 가능하다는 장점이 있다. 그리고 일정 기간을 정해 두고 삭제표시된 레코드를 시스템의 스케쥴링 작업을 통해서 데이터베이스로부터 일괄적으로 제거하면 된다.

이와 같은 soft delete는 데이터베이스와 연동해서 프로그래밍할 때는 매우 중요한 의미를 가진다. 레일스를 이용하여 웹애플리케이션을 개발할 때도 마찬가지인데, 어제 저자는 트위터에 올라온 acts_as_paranoid 라는 젬을 알게 되었다.

Gem

사용법은 간단하다.

먼저 Gem을 설치한다. Gemfile을 열고 아래와 같이 추가하고,

gem 'acts_as_paranoid'

번들 인스톨한다.

$ bundle install

Post 모델을 예를 들면 모델 클래스에 acts_as_paranoid 한 줄을 추가해 주면 된다.

class Post < ActiveRecord::Base
  acts_as_paranoid
end

그리고 여느 때와 같이 destroy 메소드로 특정 액티브레코드 객체를 삭제하면 된다.

def destroy
  @post = Post.find(params[:id])
  @post.destroy
  redirecto_to posts_path, notice: "Successfully deleted."
end

설명을 위해서 Post 모델을 아래와 같이 scaffolding 한다.

$ rails g scaffold Post title:string content:text deleted_at:datetime

이제 db:migrate 명령을 실행한 후 레일스 콘솔을 실행한다.

$ rails console

아래와 같이 콘솔 내에서 Post 클래스 객체를 생성하고 삭제한 후 복구하는 일련의 작업을 테스트해 본다.

Running via Spring preloader in process 79547
Loading development environment (Rails 5.0.0.1)
>> post = Post.create! title: "post1", content: "content1"
   (0.1ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "posts" ("title", "content", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["title", "post1"], ["content", "content1"], ["created_at", 2016-08-12 06:33:29 UTC], ["updated_at", 2016-08-12 06:33:29 UTC]]
   (0.6ms)  commit transaction
=> #
>> post.destroy
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "posts" SET deleted_at = '2016-08-12 06:33:41.977046' WHERE "posts"."deleted_at" IS NULL AND "posts"."id" = ?  [["id", 1]]
   (0.9ms)  commit transaction
=> #
>> Post.all
  Post Load (0.3ms)  SELECT "posts".* FROM "posts" WHERE "posts"."deleted_at" IS NULL
=> #
>> Post.only_deleted
  Post Load (0.1ms)  SELECT "posts".* FROM "posts" WHERE ("posts"."deleted_at" IS NOT NULL)
=> #
>> Post.only_deleted.first.recover
  Post Load (0.2ms)  SELECT  "posts".* FROM "posts" WHERE ("posts"."deleted_at" IS NOT NULL) ORDER BY "posts"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.1ms)  begin transaction
  SQL (0.3ms)  UPDATE "posts" SET "deleted_at" = ?, "updated_at" = ? WHERE "posts"."id" = ?  [["deleted_at", nil], ["updated_at", 2016-08-12 06:34:42 UTC], ["id", 1]]
   (0.7ms)  commit transaction
=> true
>> Post.all
  Post Load (0.1ms)  SELECT "posts".* FROM "posts" WHERE "posts"."deleted_at" IS NULL
=> #

자세한 내용은 공식문서를 참고하기 바란다.

글쓴이: 최효성

외과전문의,웹프로그래밍,컴퓨터 일러스트레이션 / Surgeon, Medical Illustration, Web Programmer

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중