論理削除(gem paranoia) Rails 4系

こんにちは。ikedaです。
Rails 4系で論理削除を実装する際に使われているgem paranoiaのソースを見る機会がありましたので、紹介とソースで気になった点を書いていきたいと思います。

論理削除

論理削除をざっくり説明すると、データベースのレコードからは削除せずに、削除済みであると判断できるように値を更新しておくことです。
実装のロジックは簡単でレコードが削除されているかいないかを表すカラムを追加しておき、削除する際にそのカラムを更新するというのが一般的です。

gem paranoia

その論理削除を実装することがparanoiaでできます。
gem paranoia: こちら

ちなみに, Rails3アプリケーション開発で良く使うgemまとめのスライドの中にも紹介されています。スライドで紹介されているのはacts_as_paranoidですね。

paranoiaのgithubのREADMEにも記載されていますが、paranoiaは、acts_as_paranoidよりコードを減らしているといっています。

Paranoia is a re-implementation of acts_as_paranoid for Rails 3, using much, much, much less code.

コードをみていきます

ソースコードを抜粋していくので前後のソースコードが省かれていることがあります。
ちなみに私が確認したときにはbranch: rails4 2014/04/11が最新のコミットになっていました

# paranoia
class ActiveRecord::Base
  def self.acts_as_paranoid(options={})
    alias :destroy! :destroy
    alias :delete! :delete
    # (省略)
    include Paranoia
end

destroy, deleteメソッドでdestroy!, delete!を再定義しています。
gemを使わずに開発者が論理削除を実現しようとすると,カラムを追加してupdate_attributesなどのメソッドを利用しなければなりませんが、paranoiaでは、destroy, deleteで論理削除ができるようになります。
私感ですが、論理削除であってもdestroy, deleteが扱えるので直感的に迷わないと思います。

self.paranoia_column = options[:column] || :deleted_at

デフォルトではdeleted_atのカラムを利用するようですが,options[:column]を設定することにより
カラムを指定することもできます。

例:
paranoiaで使われるカラムをdefaultのdeleted_atではなくて、suspended_atというカラムを利用するようにします。

class Student < ActiveRecord::Base
  acts_as_paranoid column: :suspended_at
end

これで、paranoiaが利用するカラムがdeleted_atからsuspended_atに切り替わります。

# paranoia
# touch paranoia column.
# insert time to paranoia column.
# @param with_transaction [Boolean] exec with ActiveRecord Transactions.
def touch_paranoia_column(with_transaction=false)
  # This method is (potentially) called from really_destroy
  # The object the method is being called on may be frozen
  # Let's not touch it if it's frozen.
  unless self.frozen?
    if with_transaction
      with_transaction_returning_status { touch(paranoia_column) }
    else
      touch(paranoia_column)
    end
  end
end

コメントにもしっかり書かれていますが、suspended_atにはtimestampが入力されます。

  # paranoia
  default_scope { where(paranoia_column => nil) }

ActiveRecordクラスからacts_as_paranoidが呼ばれると、default_scope { where(:paranoia_column => nil) }が呼ばれるので、削除されていないレコードが呼ばれます。
論理削除したレコードもみたいという場合はunscopeを利用することでとれますが、READMEを見るとwith_deleted, only_deletedメソッドなどが準備されていますからこちらを利用しましょう。

paranoiaは200行程度の小さなgemですので、読むのは比較的に楽だと思います。
gemのコードを読んでおくことでより理解が深まりますので、頻繁に利用するgemなどは読んでみると良いかもしれません。

古めの記事になりますが、こちらの記事に紹介されているgemを読んでみるのもいいと思います。

Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

ikeda

業務系のエンジニアから2013年Web系エンジニアに転身して、2014年02月よりBPS株式会社に入社。

ikedaの書いた記事

開発
静的解析ツールgem RuboCop

2014年09月11日

開発
blockの利用例 Ruby

2014年07月11日

開発
jsCafe#20に参加してきました

2014年05月17日

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ