1. ホーム
  2. ruby-on-rails

[解決済み] ActiveRecord クエリユニオン

2022-12-01 09:30:53

質問

Ruby on Railのクエリインターフェイスを使って、(少なくとも私にとっては)複雑なクエリをいくつか書きました。

watched_news_posts = Post.joins(:news => :watched).where(:watched => {:user_id => id})
watched_topic_posts = Post.joins(:post_topic_relationships => {:topic => :watched}).where(:watched => {:user_id => id})

これらのクエリは両方ともそれ自体で問題なく動作します。 どちらもPostオブジェクトを返します。 これらの投稿を1つのActiveRelationにまとめたいと思います。 何十万もの投稿がある可能性があるので、これはデータベースレベルで行う必要があります。 MySQLのクエリであれば、単純に UNION 演算子を使うことができます。 RoRのクエリインターフェイスで同じようなことができるかどうか、誰か知っていますか?

どのように解決するのですか?

複数のスコープをUNIONするための、私が書いた簡単なモジュールを紹介します。 また、結果はActiveRecord::Relationのインスタンスとして返されます。

module ActiveRecord::UnionScope
  def self.included(base)
    base.send :extend, ClassMethods
  end

  module ClassMethods
    def union_scope(*scopes)
      id_column = "#{table_name}.id"
      sub_query = scopes.map { |s| s.select(id_column).to_sql }.join(" UNION ")
      where "#{id_column} IN (#{sub_query})"
    end
  end
end

以下はその要点です。 https://gist.github.com/tlowrimore/5162327

編集する

リクエストにお応えして、UnionScopeの動作例をご紹介します。

class Property < ActiveRecord::Base
  include ActiveRecord::UnionScope

  # some silly, contrived scopes
  scope :active_nearby,     -> { where(active: true).where('distance <= 25') }
  scope :inactive_distant,  -> { where(active: false).where('distance >= 200') }

  # A union of the aforementioned scopes
  scope :active_near_and_inactive_distant, -> { union_scope(active_nearby, inactive_distant) }
end