Discardを使って論理削除を実装する方法
現在開発しているプロジェクトで論理削除を実装する必要があった為、discardというgemを利用して実装しました。
現在論理削除のgemにはparanoiaとDiscardというgemがあります。当初スターの数などでparanoiaを利用しとうかと検討したのですが、rails公式の非推奨やactive recordのコア部分を上書きしてしまう実装の為、今後のRailsバージョンアップの際や他のgemとのコンフリクトを懸念があり、discardを利用する事になりました。
discard -> https://github.com/jhawthorn/discard
paranoia -> https://github.com/rubysherpas/paranoia
実装に掛かった時間は30分程度で、非常に使いやすいgemでおすすめです。
プロジェクトの解説
SurveyItem
とSurveyItemOption
というモデルがあり、それぞれSurvey
という親モデルにbelongs_to
しています。
Gemfile
gem 'discard', '~> 1.0'
Migration
論理削除が必要なモデルにdatetimeタイプのdiscarded_at
というフィールドを追加します。論理削除する際、model.discard
というメソッドを呼ぶことでdiscarded_at
に論理削除した時間が入ります。元に戻す場合はmodel.undiscard
でフィールドをnilにする事が出来ます。
def up
add_column :survey_items, :discarded_at, :datetime, after: :deleted
add_index :survey_items, :discarded_at
add_column :survey_item_options, :discarded_at, :datetime, after: :deleted
add_index :survey_item_options, :discarded_at
end
def down
remove_column :survey_items, :discarded_at
remove_column :survey_item_options, :discarded_at
end
Model
discard gemを使うためinclude Discard::Model
をモデルincludeします。
論理削除されていないレコードだけをデフォルトで返すようにしたい為、default_scope -> { kept }
を追加します。
class SurveyItem < ApplicationRecord
include Discard::Model
default_scope -> { kept }
belongs_to :survey
end
class SurveyItemOption < ApplicationRecord
include Discard::Model
default_scope -> { kept }
belongs_to :survey
end
実装はこれだけ終わりです。
テスト
テストはminitestを使用しています。
def test_discard
assert_equal([
survey_items(:survey_item_1),
].sort,
SurveyItem.all.sort,
"Should list all records")
assert_equal([
survey_items(:survey_item_1),
].sort,
SurveyItem.kept.sort,
"Should list all records")
survey_items(:survey_item_1).discard
assert_equal([],
SurveyItem.kept,
"Should list all records")
survey = surveys(:survey_1)
assert_equal([],
survey.survey_items,
"Should have zero survey items")
end
デフォルトで論理削除されていないレコードを返すようにしているため、親レコードを取得した際、論理削除された子レコードは除かれて取得されます。素敵!
project > survey = Survey.first
=> #<Survey id: 1, company_id: 6, name: "アンケート 1", deleted: false, created_at: "2019-08-25 06:00:10", updated_at: "2019-08-25 06:00:10">
まだdiscardされていない。(まだ論理削除されていない)
project > survey.survey_items.first
=> #<SurveyItem id: 1, survey_id: 1, question_order: 1, question_name: "test", question_text: nil, question_type: nil, answer_data_type: nil, deleted: false, discarded_at: nil, created_at: "2019-09-08 11:08:57", updated_at: "2019-09-08 11:15:22">
discardする(論理削除する)
project > survey.survey_items.first.discard
=> true
project > survey = Survey.first
=> #<Survey id: 1, company_id: 6, name: "アンケート 1", deleted: false, created_at: "2019-08-25 06:00:10", updated_at: "2019-08-25 06:00:10">
survey_itemsは空で返ってくる。
project > survey = survey.survey_items
=> #<ActiveRecord::Associations::CollectionProxy []>