論理削除について

## この記事を書こうと思ったきっかけ
初心者向けにお話をする機会があって、論理削除の話をした際に資料を用意しました。たくさんスクショをとったものが残っていたこともあり、折角なので記事にしてみました。

###内容
論理削除についてです。

###対象読者
「CRUDは書けるけど、論理削除はピンとこない」という方を対象にしています。
どうやら、スクールとかでもあまり触れられないみたいです。（地味だからでしょうか？）

##論理削除とは
データを削除するときに、記憶媒体からデータを削除をしてしまうのではなく、あたかも削除されたように扱うことを論理削除といいます。（本当に削除してしまうことを物理削除といいます。）
操作間違いがあっても、データは消えていないから元に戻せますし、分析を行う際にもデータをとっておくことができるので何かと便利です。


## 論理削除を実現するには
「そのデータが論理削除されているか否か」を管理するフラグを用意します。

カラム名は何でも良いのですが、`deleted`などとして、真偽値で値を持たせたり、`deleted_at`という名前にして、タイムスタンプで持たせたり、いろいろな方法があります。「何時くらいに間違えて大量削除をしてしまったんですけど、戻せます？」みたいな時に対応をするには、タイムスタンプの方がちょっと便利かもしれません。

カラムを用意したら、論理削除フラグが立っているものを、あの手この手で削除されたかのように見せていきます。

今回はサンプルのアプリケーションをいじる形で進めていきます。削除フラグは便宜的に`flag`という名前で真偽値にしていますが、もちろんあまりいい名前ではありません。お好きな名前や型をお試しください。

##アプリケーションを準備
`[app_name]`のところに、好きなアプリケーション名をつけてください。
今回は、名前と削除フラグだけを持った`User`を作成します。

```:雛形を作成します
rails new [app_name] 
cd [app_name]
rails g scaffold User name:string flag:boolean
rails db:migrate
```

```:サーバーを起動
rails server
```

以下にアクセスします。
http://localhost:3000/users

 

<img width="674" alt="スクリーンショット 2020-05-04 21.58.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453333/de3e5888-62af-c964-2784-ceada63df457.png">

こんな画面ができたら準備完了です。

## ユーザーを登録する

それでは、2人ユーザーを登録します。一人は`flag`にチェックを入れて、もう一人は入れないでください。

<img width="674" alt="スクリーンショット 2020-05-04 22.02.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453333/bf1f0098-756d-a45a-84e7-3294c0177dc6.png">

ユーザーAの削除フラグを真にしてみました。これからコードに手を加えて、Aが消えてないのに消えているよう見せていきます。

## Aを削除されたかのように扱う

今回は`index`と`destroy`の２箇所を書きかえていきます。

### indexを書き換える
コントローラーの`index`のところをご覧ください。

```ruby:users_controller.rb
class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]
  # GET /users
  # GET /users.json
  def index
    @users = User.all  
  end
```

Aは論理削除されたユーザーなので、見えないようにしたいです。削除フラグが立っていないユーザーを取得するように書き換えてみます。

```ruby:このように書き換えます
def index
   @users = User.where(flag: false)
end
```


<img width="674" alt="スクリーンショット 2020-05-04 22.08.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453333/df89db30-4b52-66f8-d886-81aa13d24929.png">

Aが削除されたかのように見えます。

## 削除を書き換える
`scaffold`では、`destroy`アクションは物理削除になっていますので、書き換えます。単純にフラグを`true`にするように`update`します。

```ruby:users_controller.rb
 
def destroy
    @user.destroy #@user.update(flag: true)に変更
    respond_to do |format|
      format.html { redirect_to users_url, notice: 'User was successfully
destroyed.' }
      format.json { head :no_content }
    end
end
private
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
end
```

`@user`の中身がわかりにくいかもしれませんが、コールバックで`set_user`が呼ばれており、`User.find(params[:id])`が入っています。

### 削除を試してみる

それでは、Bも削除してみます。画面上では綺麗に消えたように見えます。

　<img width="674" alt="スクリーンショット 2020-05-04 22.17.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453333/c7c8f12c-313f-e9d0-d6fe-d9a57fb2dcc1.png">

画面から見えなくなっただけで、もちろんデータは存在しています。確認してみます。

```:中身を確認 
irb(main):001:0> User.all
=> #<ActiveRecord::Relation [#<User id: 1, name: "user A", flag: true,
created_at: "2020-03-29 09:30:59", updated_at: "2020-03-29 09:30:59">, #<User
id: 2, name: "User B", flag: true, created_at: "2020-03-29 09:31:09",
updated_at: "2020-03-29 09:43:01">]>
```

## スコープを使う
今回のサンプルでは`User.where(flag: false)`は論理削除されていないユーザーを取得しています。
「論理削除されたいないものを取得する」のは何度も使いますから、スコープに切り出しておくと便利です。

今回の話題とは直接関係はないのですが、スコープを作成する際には、事前の結合が必要であったりするなど特定条件下でないと機能しないようなものを作ってしまわないように気をつけてください。（いつでも使えると思って、他の人が何気なく使ってしまいます。）


記述する場所はモデルです。簡単に書き方を紹介するのに留めます。

```
scope :名前, -> {検索条件}
```
論理削除されていないユーザーをアクティブなユーザーとして、以下のように書いてみました。

```ruby:user.rb
class User < ApplicationRecord
  scope :active, -> { where(flag: false) }
  scope :deleted, -> { where(flag: true) }
end
```
詳しくはこちらもご覧ください。
[Active Recordクエリインターフェース](https://railsguides.jp/active_record_querying.html)


## コントローラーを書き換える
最後に、今作ったスコープを使って、コントローラーを書き換えてみます。

```ruby:users_controller.rb

def index
  @users = User.active
end
```

## 実際に使うときには
実際のアプリケーションに論理削除を取り入れる時は、今回のサンプルの他にまだ手を加えなければいけない箇所があります。

### 他のアクションにも対応する
今回は簡単なサンプルなので、`index`と`destroy`を見ていくだけですが、他のアクションにも手を加える必要があります。（直接パスを打って入ろうとした場合に、「存在しないユーザーです」などメッセージを出す、専用のリダイレクト先を用意しておく、など）

### 削除、削除済、以外ができないようにする
知らないうちに削除フラグのカラムの値が`true`と`false`と`nil`の3つになってしまったりすることがあるかもしれません。そうならないように、`default: false`などで確実に初期値を用意しておいたり、バリデーションを用意して`nil` を弾いたり、あるいは論理削除されていないユーザーの取得方法を工夫する必要があります。

削除フラグが`nil`のユーザーCを用意すると、今回のコードでは削除された訳でも、アクティブなわけでもないことになってしまいます。コンソールで確認してみます。

```
irb(main):001:0> User.active
=> #<ActiveRecord::Relation []>

irb(main):002:0> User.deleted
=> #<ActiveRecord::Relation
[#<User id: 1, name: "user A", flag: true, created_at: "2020-03-29 09:30:59", updated_at: "2020-03-29 09:30:59">, 
#<User id: 2, name: "User B", flag: true, created_at: "2020-03-29 09:31:09", updated_at: "2020-03-29 09:43:01">]>

irb(main):003:0> User.all
=> #<ActiveRecord::Relation
 [#<User id: 1, name: "user A", flag: true, created_at: "2020-03-29 09:30:59", updated_at: "2020-03-29 09:30:59">,
#<User id: 2, name: "User B", flag: true, created_at: "2020-03-29 09:31:09", updated_at: "2020-03-29 09:43:01">,
 #<User id: 3, name: "User C", flag: nil, created_at: "2020-05-04 19:03:27", updated_at: "2020-05-04 19:03:27">]>
```

## おしまい

お役に立てれば幸いです。ありがとうございました。