has_manyなassociationでexistsを使ったサブクエリをscopeで書いてみる。

正直言って既出な感が強いのですが、個人的な備忘として残しておきます。

# 前提
```ruby
class Parent < ActiveRecord::Base
  has_many :children, :dependent => :destroy

  def finished?
    children.unfinished.count == 0
  end
end

class Child < ActiveRecord
  belongs_to :parent

  scope :finished,   ->{ where( arel_table[:finished].eq(true) )  }
  scope :unfinished, ->{ where( arel_table[:finished].eq(false) ) }
end
```

Childは、booleanのfinishedフラグを持っており、Parentは、has_manyなchildrenがすべてfinished = trueである時にfinishedであり、それ以外はunfinishedであるとします。
この状態でunfinishedなParentをすべて取得したいという場合、SQLではexistsを使ってこんな感じに書けると思います。

```SQL
SELECT `parents`.* FROM `parents` 
WHERE EXISTS (
  SELECT `children`.* FROM `children` 
  WHERE `children`.`finished` = 0 
  AND `parents`.`id` = `children`.`parent_id`
)
```

しかし、まんまSQLで書いたのではRailsの意味がありませんので、ActiveRecordのscopeで書いて、こんな感じで呼び出せるようにしたいです。

``` ruby
Parent.unfinished
```

# とりあえず書いてみる。

というわけでscopeとして書いてみると、こんな感じでしょうか？

```ruby
class Parent < ActiveRecord::Base
  has_many :children, :dependent => :destroy

  def finished?
    children.unfinished.count == 0
  end

  scope :finished,   ->{
    children_table = Child.arel_table
    condition = Child.where( children_table[:parent_id].eq( arel_table[:id] ).and( children_table[:finished].eq(false) ) )
    where( condition.exists.not )
  }

  scope :unfinished,   ->{
    children_table = Child.arel_table
    condition = Child.where( children_table[:parent_id].eq( arel_table[:id] ).and( children_table[:finished].eq(false) ) )
    where( condition.exists )
  }
end

class Child < ActiveRecord
  belongs_to :parent

  scope :finished,   ->{ where( arel_table[:finished].eq(true) )  }
  scope :unfinished, ->{ where( arel_table[:finished].eq(false) ) }
end
```

しかし、OOPのカプセル化的にはChildがfinishとなるchildren.finished=trueという条件は、Childだけが知っていればいいことですので、Parent側に持つのは微妙かなーと。
というわけでconditionはChildに持ってもらいましょう。

# 条件部分だけをChildに移す。

```
class Parent < ActiveRecord::Base
  has_many :children, :dependent => :destroy

  def finished?
    children.unfinished.count == 0
  end

  scope :finished,   ->{ where( Child.where_unfinished_not_exists ) }
  scope :unfinished, ->{ where( Child.where_unfinished_exists ) }
end

class Child < ActiveRecord
  belongs_to :parent

  scope :finished,   ->{ where( arel_table[:finished].eq(true) )  }
  scope :unfinished, ->{ where( arel_table[:finished].eq(false) ) }

  scope :where_unfinished_exists, ->{ unfinished.where( Parent.arel_table[:id].eq( arel_table[:parent_id] ) ).exists }
  scope :where_unfinished_not_exists, ->{ unfinished.where( Parent.arel_table[:id].eq( arel_table[:parent_id] ) ).exists.not }
end
```

ついでなので、Parent.arel_table[:id].eq( Child.arel_table[:parent_id] )はParent側からscopeの引数として渡せるようにしてしまえば、
他のClassからwhere_unfinished_exists等を使いたい、という場合には再利用できますね。まぁ恐らくしないでしょうが（笑

# 条件を引数化する。
というわけで、最終的にはこうなりました。

```ruby
class Parent < ActiveRecord::Base
  has_many :children, :dependent => :destroy

  def finished?
    children.unfinished.count == 0
  end

  children_join_condition = arel_table[:id].eq( Child.arel_table[:parent_id] )
  scope :finished,   ->{ where( Child.where_unfinished_not_exists( children_join_condition ) ) }
  scope :unfinished, ->{ where( Child.where_unfinished_exists( children_join_condition ) ) }
end

class Child < ActiveRecord
  belongs_to :parent

  scope :finished,   ->{ where( arel_table[:finished].eq(true) )  }
  scope :unfinished, ->{ where( arel_table[:finished].eq(false) ) }

  scope :where_unfinished_exists, ->(cond){ unfinished.where( cond ).exists }
  scope :where_unfinished_not_exists, ->(cond){ unfinished.where( cond ).exists.not }
end
```

しかし、Parentのchildren_join_conditionがどうも冗長なんですよね。
Parentはchildrenのアソシエーションを持ってるから、そこから取り出せるんじゃ？とか思ったんですが上手くいきませんでした。

誰かいい方法をご存じないでしょうか？