diff --git a/lib/ransack/adapters/active_record/3.0/context.rb b/lib/ransack/adapters/active_record/3.0/context.rb index ec5f380..a77535b 100644 --- a/lib/ransack/adapters/active_record/3.0/context.rb +++ b/lib/ransack/adapters/active_record/3.0/context.rb @@ -68,6 +68,22 @@ module Ransack .type end + # All dependent JoinAssociation items used in the search query + # + def join_associations + @join_dependency.join_associations + end + + def join_sources + raise NotImplementedError, + "ActiveRecord 3.0 does not use join_sources or support joining relations with Arel::Join nodes. Use join_associations." + end + + def alias_tracker + raise NotImplementedError, + "ActiveRecord 3.0 does not have an alias tracker" + end + private def get_parent_and_attribute_name(str, parent = @base) diff --git a/lib/ransack/adapters/active_record/3.1/context.rb b/lib/ransack/adapters/active_record/3.1/context.rb index a167c59..cce8f21 100644 --- a/lib/ransack/adapters/active_record/3.1/context.rb +++ b/lib/ransack/adapters/active_record/3.1/context.rb @@ -73,6 +73,30 @@ module Ransack @engine.connection_pool.columns_hash[table][name].type end + def join_associations + @join_dependency.join_associations + end + + # All dependent Arel::Join nodes used in the search query + # + # This could otherwise be done as `@object.arel.join_sources`, except + # that ActiveRecord's build_joins sets up its own JoinDependency. + # This extracts what we need to access the joins using our existing + # JoinDependency to track table aliases. + # + def join_sources + base = Arel::SelectManager.new(@object.engine, @object.table) + joins = @object.joins_values + joins.each do |assoc| + assoc.join_to(base) + end + base.join_sources + end + + def alias_tracker + @join_dependency.alias_tracker + end + private def get_parent_and_attribute_name(str, parent = @base) diff --git a/lib/ransack/adapters/active_record/context.rb b/lib/ransack/adapters/active_record/context.rb index a996fe5..7693d2d 100644 --- a/lib/ransack/adapters/active_record/context.rb +++ b/lib/ransack/adapters/active_record/context.rb @@ -78,7 +78,55 @@ module Ransack end end - private + if ::ActiveRecord::VERSION::STRING >= '4.1' + + def join_associations + raise NotImplementedError, + "ActiveRecord 4.1 and later does not use join_associations. Use join_sources." + end + + # All dependent Arel::Join nodes used in the search query + # + # This could otherwise be done as `@object.arel.join_sources`, except + # that ActiveRecord's build_joins sets up its own JoinDependency. + # This extracts what we need to access the joins using our existing + # JoinDependency to track table aliases. + # + def join_sources + base = Arel::SelectManager.new(@object.engine, @object.table) + joins = @join_dependency.join_constraints(@object.joins_values) + joins.each do |aliased_join| + base.from(aliased_join) + end + base.join_sources + end + + else + + # All dependent JoinAssociation items used in the search query + # + # Deprecated: this goes away in ActiveRecord 4.1. Use join_sources. + # + def join_associations + @join_dependency.join_associations + end + + def join_sources + base = Arel::SelectManager.new(@object.engine, @object.table) + joins = @object.joins_values + joins.each do |assoc| + assoc.join_to(base) + end + base.join_sources + end + + end + + def alias_tracker + @join_dependency.alias_tracker + end + + private def get_parent_and_attribute_name(str, parent = @base) attr_name = nil diff --git a/lib/ransack/search.rb b/lib/ransack/search.rb index a6d4630..e8e5a76 100644 --- a/lib/ransack/search.rb +++ b/lib/ransack/search.rb @@ -17,7 +17,7 @@ module Ransack params = {} unless params.is_a?(Hash) (params ||= {}) .delete_if { |k, v| [*v].all? { |i| i.blank? && i != false } } - @context = Context.for(object, options) + @context = options[:context] || Context.for(object, options) @context.auth_object = options[:auth_object] @base = Nodes::Grouping.new(@context, options[:grouping] || 'and') @scope_args = {} diff --git a/spec/ransack/adapters/active_record/context_spec.rb b/spec/ransack/adapters/active_record/context_spec.rb index 3d9c076..9d18dac 100644 --- a/spec/ransack/adapters/active_record/context_spec.rb +++ b/spec/ransack/adapters/active_record/context_spec.rb @@ -6,6 +6,10 @@ module Ransack describe Context do subject { Context.new(Person) } + if ::ActiveRecord::VERSION::STRING >= "3.1" + its(:alias_tracker) { should be_a ::ActiveRecord::Associations::AliasTracker } + end + describe '#relation_for' do it 'returns relation for given object' do expect(subject.object).to be_an ::ActiveRecord::Relation @@ -30,6 +34,45 @@ module Ransack 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_associations', :if => ::ActiveRecord::VERSION::STRING <= '4.0' do + it 'returns dependent join associations for all searches run against the context' do + parents, children = shared_context.join_associations + + expect(children.aliased_table_name).to eq "children_people" + expect(parents.aliased_table_name).to eq "parents_people" + end + + it 'can be rejoined to execute a valid query' do + parents, children = shared_context.join_associations + + expect { Person.joins(parents).joins(children).to_a }.to_not raise_error + end + end + + describe '#join_sources', :if => ::ActiveRecord::VERSION::STRING >= '3.1' 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 diff --git a/spec/ransack/search_spec.rb b/spec/ransack/search_spec.rb index 66350cd..2a810a0 100644 --- a/spec/ransack/search_spec.rb +++ b/spec/ransack/search_spec.rb @@ -40,6 +40,13 @@ module Ransack it 'does not raise exception for string :params argument' do expect { Search.new(Person, '') }.not_to raise_error end + + it 'accepts a context option' do + shared_context = Context.for(Person) + search1 = Search.new(Person, {"name_eq" => "A"}, context: shared_context) + search2 = Search.new(Person, {"name_eq" => "B"}, context: shared_context) + expect(search1.context).to be search2.context + end end describe '#build' do