Reputation: 1532
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:
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. :)
Upvotes: 1
Views: 527
Reputation: 1532
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.
Upvotes: 2
Reputation: 20804
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.
Upvotes: 2