class Ransack::Adapters::ActiveRecord::Context

def alias_tracker

def alias_tracker
  @join_dependency.send(:alias_tracker)
end

def attribute_method?(str, klass = @klass)

def attribute_method?(str, klass = @klass)
  exists = false
  if ransackable_attribute?(str, klass)
    exists = true
  elsif (segments = str.split(Constants::UNDERSCORE)).size > 1
    remainder = []
    found_assoc = nil
    while !found_assoc && remainder.unshift(segments.pop) &&
    segments.size > 0 do
      assoc, poly_class = unpolymorphize_association(
        segments.join(Constants::UNDERSCORE)
        )
      if found_assoc = get_association(assoc, klass)
        exists = attribute_method?(
          remainder.join(Constants::UNDERSCORE),
          poly_class || found_assoc.klass
          )
      end
    end
  end
  exists
end

def build_association(name, parent = @base, klass = nil)

def build_association(name, parent = @base, klass = nil)
  jd = Polyamorous::JoinDependency.new(
    parent.base_klass,
    parent.table,
    Polyamorous::Join.new(name, @join_type, klass),
    @join_type
  )
  found_association = jd.instance_variable_get(:@join_root).children.last
  @associations_pot[found_association] = parent
  # TODO maybe we dont need to push associations here, we could loop
  # through the @associations_pot instead
  @join_dependency.instance_variable_get(:@join_root).children.push found_association
  # Builds the arel nodes properly for this association
  @tables_pot[found_association] = @join_dependency.construct_tables_for_association!(jd.instance_variable_get(:@join_root), found_association)
  # Leverage the stashed association functionality in AR
  @object = @object.joins(jd)
  found_association
end

def build_correlated_subquery(association)


because it is correlated to the primary key on the outer query.
The WHERE condition on this query makes it invalid by itself,

# WHERE "articles_tags"."article_id" = "articles"."id"
# INNER JOIN "tags" ON "tags"."id" = "articles_tags"."tag_id"
# SELECT "articles_tags"."article_id" FROM "articles_tags"

context.build_correlated_subquery(attribute.parent).to_sql
end
context.bind(a, a.name)
attribute = Attribute.new(context, "tags_name").tap do |a|
context = Article.search.context

Example: for an Article that has_and_belongs_to_many Tags

drawn from the first join association's foreign_key.
Build an Arel subquery that selects keys for the top query,
def build_correlated_subquery(association)
  join_constraints = extract_joins(association)
  join_root = join_constraints.shift
  correlated_key = extract_correlated_key(join_root)
  subquery = Arel::SelectManager.new(association.base_klass)
  subquery.from(join_root.left)
  subquery.project(correlated_key)
  join_constraints.each do |j|
    subquery.join_sources << Arel::Nodes::InnerJoin.new(j.left, j.right)
  end
  subquery.where(correlated_key.eq(primary_key))
end

def build_joins(relation)

reference. Lots of duplicated code maybe we can avoid it
Checkout active_record/relation/query_methods.rb +build_joins+ for
def build_joins(relation)
  buckets = relation.joins_values + relation.left_outer_joins_values
  buckets = buckets.group_by do |join|
    case join
    when String
      :string_join
    when Hash, Symbol, Array
      :association_join
    when Polyamorous::JoinDependency, Polyamorous::JoinAssociation
      :stashed_join
    when Arel::Nodes::Join
      :join_node
    else
      raise 'unknown class: %s' % join.class.name
    end
  end
  buckets.default = []
  association_joins         = buckets[:association_join]
  stashed_association_joins = buckets[:stashed_join]
  join_nodes                = buckets[:join_node].uniq
  string_joins              = buckets[:string_join].map(&:strip)
  string_joins.uniq!
  join_list = join_nodes + convert_join_strings_to_ast(relation.table, string_joins)
  alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, relation.table.name, join_list)
  join_dependency = Polyamorous::JoinDependency.new(relation.klass, relation.table, association_joins, Arel::Nodes::OuterJoin)
  join_dependency.instance_variable_set(:@alias_tracker, alias_tracker)
  join_nodes.each do |join|
    join_dependency.send(:alias_tracker).aliases[join.left.name.downcase] = 1
  end
  join_dependency
end

def build_or_find_association(name, parent = @base, klass = nil)

def build_or_find_association(name, parent = @base, klass = nil)
  find_association(name, parent, klass) or build_association(name, parent, klass)
end

def convert_join_strings_to_ast(table, joins)

def convert_join_strings_to_ast(table, joins)
  joins.map! { |join| table.create_string_join(Arel.sql(join)) unless join.blank? }
  joins.compact!
  joins
end

def evaluate(search, opts = {})

def evaluate(search, opts = {})
  viz = Visitor.new
  relation = @object.where(viz.accept(search.base))
  if search.sorts.any?
    relation = relation.except(:order)
    # Rather than applying all of the search's sorts in one fell swoop,
    # as the original implementation does, we apply one at a time.
    #
    # If the sort (returned by the Visitor above) is a symbol, we know
    # that it represents a scope on the model and we can apply that
    # scope.
    #
    # Otherwise, we fall back to the applying the sort with the "order"
    # method as the original implementation did. Actually the original
    # implementation used "reorder," which was overkill since we already
    # have a clean slate after "relation.except(:order)" above.
    viz.accept(search.sorts).each do |scope_or_sort|
      if scope_or_sort.is_a?(Symbol)
        relation = relation.send(scope_or_sort)
      else
        case Ransack.options[:postgres_fields_sort_option]
        when :nulls_first
          scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST") : Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
        when :nulls_last
          scope_or_sort = scope_or_sort.direction == :asc ? Arel.sql("#{scope_or_sort.to_sql} NULLS LAST") : Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
        when :nulls_always_first
          scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS FIRST")
        when :nulls_always_last
          scope_or_sort = Arel.sql("#{scope_or_sort.to_sql} NULLS LAST")
        end
        relation = relation.order(scope_or_sort)
      end
    end
  end
  opts[:distinct] ? relation.distinct : relation
end

def extract_correlated_key(join_root)

def extract_correlated_key(join_root)
  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)
      extract_correlated_key(join_root.right.expr)
    elsif join_root.left.is_a?(Arel::Nodes::On)
      extract_correlated_key(join_root.left.expr)
    else
      raise 'Ransack encountered an unexpected arel structure'
    end
  when Arel::Nodes::Equality
    pk = primary_key
    if join_root.left == pk
      join_root.right
    elsif join_root.right == pk
      join_root.left
    else
      nil
    end
  when Arel::Nodes::And
    extract_correlated_key(join_root.left) || extract_correlated_key(join_root.right)
  else
    # eg parent was Arel::Nodes::And and the evaluated side was one of
    # Arel::Nodes::Grouping or MultiTenant::TenantEnforcementClause
    nil
  end
end

def extract_joins(association)

def extract_joins(association)
  parent = @join_dependency.instance_variable_get(:@join_root)
  reflection = association.reflection
  join_constraints = association.join_constraints_with_tables(
                       parent.table,
                       parent.base_klass,
                       Arel::Nodes::OuterJoin,
                       @join_dependency.instance_variable_get(:@alias_tracker),
                       @tables_pot[association]
                     )
  join_constraints.to_a.flatten
end

def find_association(name, parent = @base, klass = nil)

def find_association(name, parent = @base, klass = nil)
  @join_dependency.instance_variable_get(:@join_root).children.detect do |assoc|
    assoc.reflection.name == name && assoc.table &&
    (@associations_pot.empty? || @associations_pot[assoc] == parent || !@associations_pot.key?(assoc)) &&
    (!klass || assoc.reflection.klass == klass)
  end
end

def get_association(str, parent = @base)

def get_association(str, parent = @base)
  klass = klassify parent
  ransackable_association?(str, klass) &&
  klass.reflect_on_all_associations.detect { |a| a.name.to_s == str }
end

def get_parent_and_attribute_name(str, parent = @base)

def get_parent_and_attribute_name(str, parent = @base)
  attr_name = nil
  if ransackable_attribute?(str, klassify(parent))
    attr_name = str
  elsif (segments = str.split(Constants::UNDERSCORE)).size > 1
    remainder = []
    found_assoc = nil
    while remainder.unshift(segments.pop) && segments.size > 0 &&
    !found_assoc do
      assoc, klass = unpolymorphize_association(
        segments.join(Constants::UNDERSCORE)
        )
      if found_assoc = get_association(assoc, parent)
        join = build_or_find_association(
          found_assoc.name, parent, klass
          )
        parent, attr_name = get_parent_and_attribute_name(
          remainder.join(Constants::UNDERSCORE), join
          )
      end
    end
  end
  [parent, attr_name]
end

def join_dependency(relation)

def join_dependency(relation)
  if relation.respond_to?(:join_dependency) # Polyamorous enables this
    relation.join_dependency
  else
    build_joins(relation)
  end
end

def join_sources


JoinDependency to track table aliases.
This extracts what we need to access the joins using our existing
that ActiveRecord's build_joins sets up its own JoinDependency.
This could otherwise be done as `@object.arel.join_sources`, except

All dependent Arel::Join nodes used in the search query.
def join_sources
  base, joins = begin
    alias_tracker = ::ActiveRecord::Associations::AliasTracker.create(self.klass.connection, @object.table.name, [])
    constraints   = @join_dependency.join_constraints(@object.joins_values, alias_tracker, @object.references_values)
    [
      Arel::SelectManager.new(@object.table),
      constraints
    ]
  end
  joins.each do |aliased_join|
    base.from(aliased_join)
  end
  base.join_sources
end

def klassify(obj)

def klassify(obj)
  if Class === obj && ::ActiveRecord::Base > obj
    obj
  elsif obj.respond_to? :klass
    obj.klass
  elsif obj.respond_to? :base_klass
    obj.base_klass
  else
    raise ArgumentError, "Don't know how to klassify #{obj}"
  end
end

def lock_association(association)

def lock_association(association)
  @lock_associations << association
end

def primary_key

def primary_key
  @object.table[@object.primary_key]
end

def relation_for(object)

def relation_for(object)
  object.all
end

def remove_association(association)

def remove_association(association)
  return if @lock_associations.include?(association)
  @join_dependency.instance_variable_get(:@join_root).children.delete_if { |stashed|
    stashed.eql?(association)
  }
  @object.joins_values.delete_if { |jd|
    jd.instance_variables.include?(:@join_root) &&
    jd.instance_variable_get(:@join_root).children.map(&:object_id) == [association.object_id]
  }
end

def table_for(parent)

def table_for(parent)
  parent.table
end

def type_for(attr)

def type_for(attr)
  return nil unless attr && attr.valid?
  name         = attr.arel_attribute.name.to_s
  table        = attr.arel_attribute.relation.table_name
  schema_cache = self.klass.connection.schema_cache
  unless schema_cache.send(:data_source_exists?, table)
    raise "No table named #{table} exists."
  end
  attr.klass.columns.find { |column| column.name == name }.type
end