Skip to content

Commit

Permalink
Fix inverts search, when have additional conditions in scope (like de…
Browse files Browse the repository at this point in the history
…fault_scope)
  • Loading branch information
le0pard committed Sep 14, 2024
1 parent 2d56e78 commit 8fea98d
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 23 deletions.
35 changes: 35 additions & 0 deletions lib/ransack/adapters/active_record/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,11 @@ def build_correlated_subquery(association)
join_constraints = extract_joins(association)
join_root = join_constraints.shift
correlated_key = extract_correlated_key(join_root)
join_conditions = extract_join_conditions(join_root, correlated_key)
subquery = Arel::SelectManager.new(association.base_klass)
subquery.from(join_root.left)
subquery.project(correlated_key)
subquery.where(join_conditions) if join_conditions
join_constraints.each do |j|
subquery.join_sources << Arel::Nodes::InnerJoin.new(j.left, j.right)
end
Expand Down Expand Up @@ -209,6 +211,39 @@ def extract_correlated_key(join_root)
end
end

def extract_join_conditions(join_root, correlated_key)
case join_root
when Arel::Nodes::OuterJoin
# one of join_root.right/join_root.left is expected to be Arel::Nodes::On
if join_root.right.is_a?(Arel::Nodes::On)
join_root.right.expr = extract_join_conditions(join_root.right.expr, correlated_key)
elsif join_root.left.is_a?(Arel::Nodes::On)
join_root.right.expr = extract_join_conditions(join_root.left.expr, correlated_key)
else
raise 'Ransack encountered an unexpected arel structure'
end
when Arel::Nodes::Grouping
join_root.expr = extract_join_conditions(join_root.expr, correlated_key)
when Arel::Nodes::Equality
pk = primary_key

if (join_root.left == pk && join_root.right == correlated_key) ||
(join_root.right == pk && join_root.left == correlated_key)
return nil # skip correlated_key node
end

join_root
when Arel::Nodes::And
filtered_children = join_root.children.filter_map do |child|
extract_join_conditions(child, correlated_key)
end

Arel::Nodes::And.new(filtered_children)
else
join_root
end
end

def get_parent_and_attribute_name(str, parent = @base)
attr_name = nil

Expand Down
48 changes: 27 additions & 21 deletions spec/ransack/adapters/active_record/context_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,39 +44,45 @@ module ActiveRecord
search = Search.new(Person, { articles_title_not_eq: 'some_title' }, context: subject)
attribute = search.conditions.first.attributes.first
constraints = subject.build_correlated_subquery(attribute.parent).constraints
constraint = constraints.first

expect(constraints.length).to eql 1
expect(constraint.left.name).to eql 'person_id'
expect(constraint.left.relation.name).to eql 'articles'
expect(constraint.right.name).to eql 'id'
expect(constraint.right.relation.name).to eql 'people'
equality_constraint = constraints.detect { |c| c.is_a?(Arel::Nodes::Equality) }
and_constraints = constraints.detect { |c| c.is_a?(Arel::Nodes::And) }

expect(constraints.length).to eql 2
expect(equality_constraint.left.name).to eql 'person_id'
expect(equality_constraint.left.relation.name).to eql 'articles'
expect(equality_constraint.right.name).to eql 'id'
expect(equality_constraint.right.relation.name).to eql 'people'
expect(and_constraints.to_sql).to include('default_scope')
end

it 'build correlated subquery for Child STI model when predicate is not_eq' do
search = Search.new(Person, { story_articles_title_not_eq: 'some_title' }, context: subject)
attribute = search.conditions.first.attributes.first
constraints = subject.build_correlated_subquery(attribute.parent).constraints
constraint = constraints.first

expect(constraints.length).to eql 1
expect(constraint.left.relation.name).to eql 'articles'
expect(constraint.left.name).to eql 'person_id'
expect(constraint.right.relation.name).to eql 'people'
expect(constraint.right.name).to eql 'id'
equality_constraint = constraints.detect { |c| c.is_a?(Arel::Nodes::Equality) }
and_constraints = constraints.detect { |c| c.is_a?(Arel::Nodes::And) }

expect(constraints.length).to eql 2
expect(equality_constraint.left.relation.name).to eql 'articles'
expect(equality_constraint.left.name).to eql 'person_id'
expect(equality_constraint.right.relation.name).to eql 'people'
expect(equality_constraint.right.name).to eql 'id'
expect(and_constraints.to_sql).to include('default_scope')
end

it 'build correlated subquery for Child STI model when predicate is eq' do
search = Search.new(Person, { story_articles_title_not_eq: 'some_title' }, context: subject)
attribute = search.conditions.first.attributes.first
constraints = subject.build_correlated_subquery(attribute.parent).constraints
constraint = constraints.first

expect(constraints.length).to eql 1
expect(constraint.left.relation.name).to eql 'articles'
expect(constraint.left.name).to eql 'person_id'
expect(constraint.right.relation.name).to eql 'people'
expect(constraint.right.name).to eql 'id'
equality_constraint = constraints.detect { |c| c.is_a?(Arel::Nodes::Equality) }
and_constraints = constraints.detect { |c| c.is_a?(Arel::Nodes::And) }

expect(constraints.length).to eql 2
expect(equality_constraint.left.relation.name).to eql 'articles'
expect(equality_constraint.left.name).to eql 'person_id'
expect(equality_constraint.right.relation.name).to eql 'people'
expect(equality_constraint.right.name).to eql 'id'
expect(and_constraints.to_sql).to include('default_scope')
end

it 'build correlated subquery for multiple conditions (default scope)' do
Expand Down
2 changes: 0 additions & 2 deletions spec/ransack/search_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ module Ransack
expect(s.result.to_sql).to include 'published'
end

# The failure/oversight in Ransack::Nodes::Condition#arel_predicate or deeper is beyond my understanding of the structures
it 'preserves (inverts) default scope and conditions for negative subqueries' do
# the positive case (published_articles_title_eq) is
# SELECT "people".* FROM "people"
Expand Down Expand Up @@ -178,7 +177,6 @@ module Ransack
# AND "articles"."title" = 'Test' AND "articles"."published" = 't' AND ('default_scope' = 'default_scope')
# ) ORDER BY "people"."id" DESC

pending("spec should pass, but I do not know how/where to fix lib code")
s = Search.new(Person, published_articles_title_not_eq: 'Test')
expect(s.result.to_sql).to include 'default_scope'
expect(s.result.to_sql).to include 'published'
Expand Down

0 comments on commit 8fea98d

Please sign in to comment.