

以下のように一般用と管理用のscopeを持ったモデルがあります。

[code]

    class Parent < ActiveRecord::Base
      has_many :children
    
      # 一般的に使用するスコープ
      default_scope do
        # 論理削除を含まない
        unscoped.where(deleted_at: nil).includes(:children)
      end
    
      # ActiveAdminなどで使用するスコープ
      def self.admin_scope do 
        # 論理削除を含む
        unscoped.includes(:children) 
      end
    end
    
    class Child < ActiveRecord::Base
      belongs_to :parent
    
      default_scope do
        unscoped.where(deleted_at: nil)
      end
    
      def self.admin_scope do
        unscoped
      end
    end
    
[/code]

例えばParentの id = 1 に3つのchildがDB上に記録されているが、1つは論理削除されている場合に、以下のような動きにしたいです。

[code]

    m1 = Parent.admin_scope.find(1)
    m1.children.length # => 3
    
    m2 = Parent.find(1)
    m2.children.length # => 2
    
[/code]

こうしたコードをうまく書く方法はあるでしょうか？


CC BY-SA 3.0
2015-02-22T07:34:20.670
6914
2015-07-22T14:08:17.420
2015-02-22T09:10:30.123
85
8473
question
ruby-on-rails
Rails - 親オブジェクトで使ったscopeに応じて子オブジェクトのscopeも切り替える方法を教えてください


Parentのインスタンスはどのscopeを使われたか覚えていないので、以下のようにchildernの呼び出し時に毎回気を付けてやるしかない気がします。

[code]

    m1 = Parent.admin_scope.find(1)
    m1.children.admin_scope.length # => 3
    
    m2 = Parent.find(1)
    m2.children.length # => 2
    
[/code]

### （オススメしない解決策）

おそらくですが、以下のように書いてもそれっぽく（？）動くと思います。

[code]

    m1 = Parent
           .admin_scope
           .includes(:children)
           .references(:children)
           .merge(Child.admin_scope)
           .find(1)
    m1.children.length # => 3
    
[/code]

しかし、`m1.children.count`を呼ぶと、戻り値は`2`になると思います。

以下はその理屈です。

最初に`m1`を取得した時点では、削除された`children`も含まれます。（`Child#admin_scope`の条件を含むSQLが発行されるため）

`m1.children.length`や`m1.children.each`ではSQLが再発行されないため、3件のままキープされます。

しかし、`m1.children.count`はカウント取得用のSQLを再度発行します。このときは`admin_scope`の条件が外れるために、`3`が返ってきます。

同じく、`m1.children.reload`とした場合も`admin_scope`の条件が外れた状態でSQL再発行するために`m1.children.length`は`3`に戻ってしまいます。

このように「どこでRailsがSQLを再発行するか？」ということをしっかり理解していないと、予期せぬ不具合の原因になります。  
そのためプログラマのうっかりミスやRailsの仕様変更で`admin_scope`が外れてしまいかねません。

なので、できるだけこの方法は採用せず、最初に示した`m1.children.admin_scope.length`（毎回明示的にscopeを指定する方法）を使う方が良いと思います。


CC BY-SA 3.0
2015-02-22T09:08:54.190
6917
2015-02-22T09:08:54.190
85
6914
answer