Railsで削除フラグを使うのにparanoiaが使えた

Railsのテーブル設計から外れたデータベースを使う実装で使えたのでメモ。

論理削除のgemで有名な`paranoa`を使って`deleted_at`カラムを使って論理削除を実現できますが、
既存データベースで`delete_flg`のような名前で論理削除をしているケースでもある程度対応できました。
※ただし、若干モンキーパッチ的な部分があり、本来の`deleted_at`のような削除日時のカラムもある場合、共存が難しそうです。

### 検証環境
`Rails 5.2`
`MySQL 5.7`
### 検証
以下のテーブルがあるとします。

**samples**

|カラム名|データ型|用途|
|---|---|---|
|id|integer||
|name|string||
|delete_flg|tinyint|0: 削除されていない<br>1: 削除|

`paranoia`gemを追加します。

```rb:Gemfile
gem 'paranoia'
```



以下のようにモデルを作成します。

```rb:app/models/sample.rb
class Sample < ApplicationRecord
  # tinyintの項目を論理削除用カラムとして指定
  acts_as_paranoid column: :delete_flg
end
```

以下がポイントです。
ここで「削除でない場合の値」を指定しておくことで既存データベースでも使えるようになります。

```rb:config/initializers/paranoia.rb
module Paranoia
  # 削除じゃない状態の値を上書き 元々の値: nil
  @@default_sentinel_value = false
end
```

ちなみに`deleted_at`のカラム名のデフォルト値も変更しようと思ったのですが、
https://github.com/rubysherpas/paranoia/blob/core/lib/paranoia.rb#L244
で

```rb:paranoia/lib/paranoia.rb
self.paranoia_column = (options[:column] || :deleted_at).to_s
```

のように直接記述されており、変更するのに骨が折れるので放置しました。

rails consoleで検証します。

```irb_output
# 作成して保存
irb(main):001:0> sample = Sample.new(name: 'test')
   (0.9ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<Sample id: nil, name: "test", delete_flg: false, created_at: nil, updated_at: nil>
irb(main):002:0> sample.save
   (0.6ms)  BEGIN
  Sample Create (0.5ms)  INSERT INTO `samples` (`name`, `delete_flg`, `created_at`, `updated_at`) VALUES ('test', FALSE, '2019-08-13 04:13:40', '2019-08-13 04:13:40')
   (2.7ms)  COMMIT
=> true
# 削除フラグはFALSEで保存される。

# 削除
irb(main):003:0> sample.delete
  Sample Update (4.3ms)  UPDATE `samples` SET `delete_flg` = TRUE, `updated_at` = '2019-08-13 04:13:48' WHERE `id` = 1
=> #<Sample id: 1, name: "test", delete_flg: true, created_at: "2019-08-13 04:13:40", updated_at: "2019-08-13 04:13:48">
# 削除フラグにtrueが入る

# 検索
irb(main):004:0> Sample.all
  Sample Load (1.6ms)  SELECT  `samples`.* FROM `samples` WHERE `delete_flg` = FALSE LIMIT 11
=> #<ActiveRecord::Relation []>
# 削除フラグがfalseのものだけ検索

# 削除を戻す
irb(main):005:0> sample.restore
   (0.5ms)  BEGIN
  Sample Update (0.7ms)  UPDATE `samples` SET `delete_flg` = FALSE, `updated_at` = '2019-08-13 04:13:58' WHERE `id` = 1
   (2.6ms)  COMMIT
=> #<Sample id: 1, name: "test", delete_flg: false, created_at: "2019-08-13 04:13:40", updated_at: "2019-08-13 04:13:58">
# 削除フラグがFALSEに更新される

# 再び検索
irb(main):006:0> Sample.all
  Sample Load (0.8ms)  SELECT  `samples`.* FROM `samples` WHERE `delete_flg` = FALSE LIMIT 11
=> #<ActiveRecord::Relation [#<Sample id: 1, name: "test", delete_flg: false, created_at: "2019-08-13 04:13:40", updated_at: "2019-08-13 04:13:58">]>
# 検索された
```

### 問題点
通常の`deleted_at`を持つ以下のテーブルを用意

**sample2**

|カラム名|データ型|用途|
|---|---|---|
|id|integer||
|name|string||
|deleted_at|datetime|値あり: 削除|

```rb:app/models/sample.rb
class Sample2 < ApplicationRecord
  # 通常通りに作成
  acts_as_paranoid
end
```

rails consoleで実験

```irb_output
# 保存
irb(main):001:0> sample2 = Sample2.new(name: 'hoge')
   (0.4ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
=> #<Sample2 id: nil, name: "hoge", deleted_at: nil, created_at: nil, updated_at: nil>
irb(main):002:0> sample2.save
   (0.6ms)  BEGIN
  Sample2 Create (0.6ms)  INSERT INTO `sample2s` (`name`, `created_at`, `updated_at`) VALUES ('hoge', '2019-08-13 04:55:22', '2019-08-13 04:55:22')
   (3.9ms)  COMMIT
=> true

# 削除
irb(main):003:0> sample2.delete
  Sample2 Update (2.5ms)  UPDATE `sample2s` SET `deleted_at` = '2019-08-13 04:55:27', `updated_at` = '2019-08-13 04:55:27' WHERE `id` = 1
=> #<Sample2 id: 1, name: "hoge", deleted_at: "2019-08-13 04:55:27", created_at: "2019-08-13 04:55:22", updated_at: "2019-08-13 04:55:27">

# 復元
irb(main):004:0> sample2.restore
   (1.2ms)  BEGIN
  Sample2 Update (1.1ms)  UPDATE `sample2s` SET `deleted_at` = 0, `updated_at` = '2019-08-13 04:55:32' WHERE `id` = 1
   (0.8ms)  ROLLBACK
Traceback (most recent call last):
        1: from (irb):4
ActiveRecord::StatementInvalid (Mysql2::Error: Incorrect datetime value: '0' for column 'deleted_at' at row 1: UPDATE `sample2s` SET `deleted_at` = FALSE, `updated_at` = '2019-08-13 04:55:32' WHERE `id` = 1)
# datetime型にfalseを入れようとして怒られる。
```
上記のように復元でエラーが発生してしまいます。
※このあたりもモンキーパッチで対応可能とは思いますが、あまり書き換えすぎるとバージョンアップに対応できなくなりそうで控えておきたい所です。