Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix inverts search, when have additional conditions in scope (like default_scope) #1526

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading