Testing Context Query Building and Evaluation in Ransack
This test suite examines the Context class functionality within Ransack’s ActiveRecord adapter. It verifies search query building, relationship handling, and SQL generation capabilities across different ActiveRecord versions.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
activerecord-hackery/ransack
spec/ransack/adapters/active_record/context_spec.rb
require 'spec_helper'
module Ransack
module Adapters
module ActiveRecord
version = ::ActiveRecord::VERSION
AR_version = "#{version::MAJOR}.#{version::MINOR}"
describe Context do
subject { Context.new(Person) }
it 'has an Active Record alias tracker method' do
expect(subject.alias_tracker)
.to be_an ::ActiveRecord::Associations::AliasTracker
end
describe '#relation_for' do
it 'returns relation for given object' do
expect(subject.object).to be_an ::ActiveRecord::Relation
end
end
describe '#evaluate' do
it 'evaluates search objects' do
s = Search.new(Person, name_eq: 'Joe Blow')
result = subject.evaluate(s)
expect(result).to be_an ::ActiveRecord::Relation
expect(result.to_sql)
.to match /#{quote_column_name("name")} = 'Joe Blow'/
end
it 'SELECTs DISTINCT when distinct: true' do
s = Search.new(Person, name_eq: 'Joe Blow')
result = subject.evaluate(s, distinct: true)
expect(result).to be_an ::ActiveRecord::Relation
expect(result.to_sql).to match /SELECT DISTINCT/
end
end
describe '#build_correlated_subquery' do
it 'build correlated subquery for Root STI model' do
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'
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'
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'
end
it 'build correlated subquery for multiple conditions (default scope)' do
search = Search.new(Person, { comments_body_not_eq: 'some_title' })
# Was
# SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
# SELECT "comments"."disabled" FROM "comments"
# WHERE "comments"."disabled" = "people"."id"
# AND NOT ("comments"."body" != 'some_title')
# ) ORDER BY "people"."id" DESC
# Should Be
# SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
# SELECT "comments"."person_id" FROM "comments"
# WHERE "comments"."person_id" = "people"."id"
# AND NOT ("comments"."body" != 'some_title')
# ) ORDER BY "people"."id" DESC
expect(search.result.to_sql).to match /.comments.\..person_id. = .people.\..id./
end
end
describe 'sharing context across searches' do
let(:shared_context) { Context.for(Person) }
before do
Search.new(Person, { parent_name_eq: 'A' },
context: shared_context)
Search.new(Person, { children_name_eq: 'B' },
context: shared_context)
end
describe '#join_sources' do
it 'returns dependent arel join nodes for all searches run against
the context' do
parents, children = shared_context.join_sources
expect(children.left.name).to eq "children_people"
expect(parents.left.name).to eq "parents_people"
end
it 'can be rejoined to execute a valid query' do
parents, children = shared_context.join_sources
expect { Person.joins(parents).joins(children).to_a }
.to_not raise_error
end
end
end
it 'contextualizes strings to attributes' do
attribute = subject.contextualize 'children_children_parent_name'
expect(attribute).to be_a Arel::Attributes::Attribute
expect(attribute.name.to_s).to eq 'name'
expect(attribute.relation.table_alias).to eq 'parents_people'
end
it 'builds new associations if not yet built' do
attribute = subject.contextualize 'children_articles_title'
expect(attribute).to be_a Arel::Attributes::Attribute
expect(attribute.name.to_s).to eq 'title'
expect(attribute.relation.name).to eq 'articles'
expect(attribute.relation.table_alias).to be_nil
end
end
end
end
end