with_scopeとalias_method_chainを使って論理削除の実装

注意

このエントリーは、railsで論理削除を実装しようとするときに、プラグインとか使わなかった、使えなかった、今更使うの?みたいな感じになったときに論理削除をサポートしていくための、最初のステップを書いています。まずは

  1. プラグインを使えないのか?、
  2. 論理削除を実装しなければならないかどうか?

検討してみてくださいね。

参考:論理削除をサポートしてくれるrailsのプラグイン(一例として)

論理削除を手動で実装

例えば、学校(schools)と学生(students)というclassを作る。んで、学生には論理削除をつけましょう。

DB

schools
  :name, :string
  ...

students
  :family_name, :string
  :first_name, :string
  ...
  :status, :enum, :limit => [:active, :inactive], :default => :active
  # 今回はenumカラムを使って、inactiveというステータスを持っていたら、論理削除されている、ということにします。

関係は学校 has_many 学生かな

Model

class School < ActiveRecord::Base
  has_many :students
# 中略

end

class Student < ActiveRecord::Base
  belongs_to :schools
# 中略
  def destroy
    self.status = :inactive
    self.save
  end
end

このときに便利なActiveRecordのおかげで、以下のような参照関係を得られます。

school.students
student.school

例えば、東大に、山田君と佐藤さんがいたとすると、

school
#=> 東大

school.students
#=> [山田君, 佐藤さん]

みたいに得られるはず。

ここで山田君、東大を退学しました

yamadakun.destory
#=> yamadakun.status == :inactive

また東大の学生を参照すると、

school.students
#=> [山田君, 佐藤さん]

山田君まだいる( ゜Д゜)!?

ということで、手動で実装すると、論理削除はめんどくなります。

論理削除に合わせるためにfindなどのDB参照系のメソッドを上書き

しょうが無いので、findやらなんやら上書きしていきます。
基本的に論理削除がある StudentClassのメソッドを上書きします。

このときに使うのが、with_scopeとalias_method_chain

class << self
  def find_with_active(*args)
    with_scope(:find => {:conditions =>["status = ?", :active]}) do
      find_without_active(*args)
    end
  end
  alias_method_chain :find, :active

  def count_with_active(*args)
    with_scope(:count => {:conditions =>["status = ?", :active]}) do
      count_without_active(*args)
    end
  end
  alias_method_chain :count, :active
end

必要なものは必要なだけ書かなきゃいけない。
ちなみにfind_by_sqlとかには対応できない。
(その時はちゃんとstatusも含めて書いてね、ということで)


ここまでしてあげれば、さっきの山田君もちゃんと論理削除されて見えます。

school.students
#=> [佐藤さん]

テスト

必要なものを必要なだけテストしてください。

注意点はfixturesの読み込み。
当然のようにfixturesを読み込むときには、findメソッドが使われているわけで、
fixturesの中で、論理削除されたレコードを作っておくと、そのレコードが読み込めないためERRORを起こします。

テストの中で初期化するような部分とかで、ちゃんと論理削除してあげてください。