Question

The setup.

I am trying to implement a complex search query via RoR's ActiveRecord::Relation.

So far the code inside the index action code looks as follows:

query = Member.all
query = query.search(params[:q])
query = query.includes(:structures)
query = query.with_structure(params[:party_id])
query = query.with_structure(params[:assembly_id])

@members = query.paginate(:page => params[:page])

The idea behind the code is that I get all Members and then remove members when a filter is active. search and with_structures are custom class methods inside which the where clauses are performed.

Sidenote: Because of this, if your solution involves using arel, make sure that you show me how to convert AREL::NODES back into ActiveRecord::Relation

My DB looks as follows:

enter image description here

A member can change his position within a structure, thus he/she can have many participations with one/the same structure.

The problem

When both party and assembly filters are active the query returns nothing, because it performs multiple where clauses on the same column.

What I tried

Member.joins(:structures)
.where(structures: { id: [1303, 1181] })
.group("members.id").count

or if you prefer the SQL:

SELECT COUNT(*) AS count_all, members.id AS members_id
FROM "members"
INNER JOIN "participations" ON "participations"."member_id" = "members"."id"
INNER JOIN "structures" ON "structures"."id" = "participations"."structure_id"
WHERE "structures"."id" IN (1303, 1181)
GROUP BY members.id
HAVING count(*)>1

This doesn't work, because many records have a count > 1, but are part of only one of the two structures and using OUTER LEFT JOIN doesn't help either.

I also tried using arel, but I was not able to convert back to ActiveRecord. I tried using q1.intersect q2.

What I want

I want to be able to apply both where statements without them cancelling each other out. I want to be able to write something in like this:

query = Member.all
q1 = query.with_structure(params[:party_id])
q2 = query.with_structure(params[:assembly_id])
query = q1.intersect q2

Thank you in advance, and please do not hasitate to ask quetions if something is not clear. :)

Was it helpful?

Solution

You have to join to participations twice, once for each id.

 from members m join participation p1
 on m.id = p1.member_id and p1.structure_id = 1303
 join participation p2 on m.id = p2.member_id and p2.structure_id = 1181

I'm not sure if you need to join to structures, because you don't appear to be selecting from it.

OTHER TIPS

I want to share with SO what I managed as a solution to the problem. I am not going to accept my own answer as THE answer, because @Dan Bracuk helped me a bit and it wouldn't be fair.

In the controller:

query = Member.all
query = query.search(params[:q])

ids = params.slice(:party_id, :assembly_id).values.delete_if { |v| v.blank? }.map { |v| v.to_i }
query = query.create_joins ids

@members = query.paginate(:page => params[:page])

This is inside the members model:

def self.create_joins structure_ids
  if structure_ids.empty?
    all
  else
    structure_ids.each_with_index.map do |id, idx|
      joins("INNER JOIN 'participations' 'p#{idx}' ON 'p#{idx}'.'member_id' = 'members'.'id'")
      .where("p#{idx}.structure_id" => id).uniq
    end.reduce(:merge)
  end
end

The idea is that we create a separate join and query for every id that is inside sructure_ids and then we simply merge them together using the reduce method.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top