2016-10-08 18 views
1

1つ以上のタグを持つすべての投稿をユーザーが検索できるようにしたい。また、タグを追加基準にしたいとします。たとえば、「ニュース」タグだけの記事を検索することも、「ニュース」タグと「サイエンス」タグの両方を持つ投稿を検索することもできます。has_manyの追加範囲の条件:through

現在、私が持っているのは、Postモデル、Tagモデル、Markingという結合モデルです。投稿has_many :tags, through: :markings。私はPostクラスメソッドにタグIDの配列を渡すことにより、必要なものを取得:

post.rb

def self.from_tag_id_array array 
    post_array = [] 
    Marking.where(tag_id: array).group_by(&:post_id).each do |p_id,m_array| 
    post_array << p_id if m_array.map(&:tag_id).sort & array.sort == array.sort 
    end 
    where id: post_array 
end 

は、これはそこに不格好な方法のように思えます。私が協会などのスコープでこれを行う方法はありますか?

+1

このようなことを試すことができます。 http://stackoverflow.com/questions/25606775/how-to-find-posts-tagged-with-more-than-one-tag-in-rails-and-postgresql – Jayaprakash

答えて

2

この種のクエリを構築する上での一般的な経験則は、「Ruby-land」での作業を最小限に抑え、「Database-land」での作業を最大限にすることです。上記のソリューションでは、セットarrayのタグを使用してマーキングのセットを取得しています。これはおそらく、非常に大きなセット(それらのタグを持つすべての投稿)になります。これはルビー配列で表現され、処理されます(group_byはRuby-world、groupはDatabase-landで同等です)。

読みにくいというだけでなく、その解決策は大きなマークセットでは遅くなります。

Ruby-Worldで重労働を起こさずに問題を解決する方法はいくつかあります。一つの方法は、このように、サブクエリを使用している:

scope :with_tag_ids, ->(tag_ids) { 
    tag_ids.map { |tag_id| 
    joins(:markings).where(markings: { tag_id: tag_id }) 
    }.reduce(all) { |scope, subquery| scope.where(id: subquery) } 
} 

これは、ここですべてがSQLで直接計算されているためという

SELECT "posts".* 
FROM "posts" 
WHERE "posts"."id" IN (SELECT "posts"."id" FROM "posts" INNER JOIN "markings" ON "markings"."post_id" = "posts"."id" WHERE "markings"."tag_id" = 5) 
    AND "posts"."id" IN (SELECT "posts"."id" FROM "posts" INNER JOIN "markings" ON "markings"."post_id" = "posts"."id" WHERE "markings"."tag_id" = 8) 

注意、無アレイ(tag_ids 5と8のために再び)このようなクエリを生成し、 Rubyで生成または処理されます。これは一般的にははるかに優れています。

また、あなたはCOUNTを使用して、サブクエリなしで単一のクエリでそれを行うことができます。

このようなSQLを生成
scope :with_tag_ids, ->(tag_ids) { 
    joins(:markings).where(markings: { tag_id: tag_ids }). 
    group(:post_id).having('COUNT(posts.id) = ?', tag_ids.count) 
} 

SELECT "posts".* 
FROM "posts" 
INNER JOIN "markings" ON "markings"."post_id" = "posts"."id" 
WHERE "markings"."tag_id" IN (5, 8) 
GROUP BY "post_id" 
HAVING (COUNT(posts.id) = 2) 

これは、あなたが複数のマーキングを持っていないことを前提としていtag_idpost_idの同じペアを使用してカウントを停止します。

最後の解決策はおそらく最も効率的だと思いますが、さまざまな解決策を試して、自分のデータに最も適したものを見てください。

も参照してください。Query intersection with activerecord

+0

あなたの経験則は本当に役に立ちます。私はいくつかの読書を持っている。ありがとう! – eeeeeean

関連する問題