ActiveRecord4でpolymorphicなScoreの値の合計値でソートしたい。


TeamモデルとPlayerモデルがhas_manyの関係でpolymorphicなScoreモデルを持つというような構成で
チームやプレイヤーに得点を付けることができるシステムを作成しています。

チームに所属しているプレイヤーの一覧を表示するときに Scoreモデルが持つvalueカラムの合計値を使ってソートをかけたいです。

Playerモデルに以下のスコープを作成しました。

[code]

    scope :sorted, -> {
      joins(:scores)
      .group("players.id")
      .order("sum(scores.value) desc")
    }
    
[/code]

しかし、これだとひとつもスコアを付けられていない要素が含まれずに スコアを付けられている要素の中でソートが行われてしまいます。

スコアが付けられていない要素に対してはデフォルトで０点ということにして 全ての要素を取得したいのですがどうすればよいでしょうか？




似たようなモデル構造のプロジェクトがあったので、実際に動かしながら試してみました。

回答からまず書くと、たぶんこんな感じでいけるんじゃないかと思います。

[code]

    scope :sorted, -> {
      joins("LEFT OUTER JOIN scores ON scores.scorable_id = players.id AND scores.scorable_type = 'Player'")
      .group("players.id")
      .order("COALESCE(SUM(scores.value), 0) DESC")
    }
    
[/code]

`scorable` というのはこちらの想像で付けた名前です。ここは実際のカラムの名前に変更してください。  
ちなみにクラス定義でいうと、こんな定義をイメージしています。

[code]

    class Player
      has_many :scores, as: :scorable
    
    class Score
      belongs_to :scorable, polymorphic: true
    
[/code]

あと、`COALESCE` というのはPostgresでNULL（=スコアが付けられていない要素）を2番目の引数の値（=0）に変更する関数です。  
使用するRDBMSによっては別の関数を使うかもしれません。

補足事項として、Railsで外部結合（LEFT OUTER JOIN）を使う場合は、`includes` + `references`
をよく使うのですが、これを使うと scores のカラムも一緒に取得しようとするためにSQLエラーが起きます。

コードで書くとこんな感じです。（エラーが起きる）

[code]

    scope :sorted, -> {
      includes(:scores)
      .references(:scores)
      .group("players.id")
      .order("COALESCE(SUM(scores.value), 0) DESC")
    }
    
[/code]

なので、今回は（極力避けたい）生のSQLでテーブルをJOINしています。

あと、もう一点だけ。  
KoRoN さんも書いているように、[Squeel](https://github.com/activerecord-
hackery/squeel)というgemを使うとJOINをスッキリ書けます。

[code]

    scope :sorted, -> {
      joins{ scores.outer }
      .group("players.id")
      .order("COALESCE(SUM(scores.value), 0) DESC")
    }
    
[/code]

以上、ご参考までに。




似たようなモデル構造のプロジェクトがあったので、実際に動かしながら試してみました。

回答からまず書くと、たぶんこんな感じでいけるんじゃないかと思います。

[code]

    scope :sorted, -> {
      joins("LEFT OUTER JOIN scores ON scores.scorable_id = players.id AND scores.scorable_type = 'Player'")
      .group("players.id")
      .order("COALESCE(SUM(scores.value), 0) DESC")
    }
    
[/code]

`scorable` というのはこちらの想像で付けた名前です。ここは実際のカラムの名前に変更してください。  
ちなみにクラス定義でいうと、こんな定義をイメージしています。

[code]

    class Player
      has_many :scores, as: :scorable
    
    class Score
      belongs_to :scorable, polymorphic: true
    
[/code]

あと、`COALESCE` というのはPostgresでNULL（=スコアが付けられていない要素）を2番目の引数の値（=0）に変更する関数です。  
使用するRDBMSによっては別の関数を使うかもしれません。

補足事項として、Railsで外部結合（LEFT OUTER JOIN）を使う場合は、`includes` + `references`
をよく使うのですが、これを使うと scores のカラムも一緒に取得しようとするためにSQLエラーが起きます。

コードで書くとこんな感じです。（エラーが起きる）

[code]

    scope :sorted, -> {
      includes(:scores)
      .references(:scores)
      .group("players.id")
      .order("COALESCE(SUM(scores.value), 0) DESC")
    }
    
[/code]

なので、今回は（極力避けたい）生のSQLでテーブルをJOINしています。

あと、もう一点だけ。  
KoRoN さんも書いているように、[Squeel](https://github.com/activerecord-
hackery/squeel)というgemを使うとJOINをスッキリ書けます。

[code]

    scope :sorted, -> {
      joins{ scores.outer }
      .group("players.id")
      .order("COALESCE(SUM(scores.value), 0) DESC")
    }
    
[/code]

以上、ご参考までに。

