is there a way to get back the where conditions in an ActiveRecord::Relation?

27 Views Asked by At

If I do:

rel = Foo.where(:foo_field => 123).joins(:bars).where(:bars => { :xyz => 123 });

Is there a way to get back the bars conditions { :xyz => 123 } from that relation?

I see that I can do rel.where_clause.to_h but that gives me { :foo_field => 123, :xyz => 123 } and there's no way to differentiate what was specifically for the bars join... Is there a way to get back JUST the bars conditions?

1

There are 1 best solutions below

1
Robert Nubel On

With the caveat that there's probably an easier way to accomplish what you're ultimately trying to do, you can use the information in the underlying Arel AST to suss out which where conditions are attached to which table. This only works if they're specified in hash form -- literal SQL conditions, for example, wouldn't be parsed by Arel to know which table they involve.

Here's what I came up with, using a recursive method that does a depth-first search to identify the expressions we're interested in:

# tested on ActiveRecord v7.0.6 (the APIs used here are
# internal and not guaranteed to be stable for future versions)

def collect_where_nodes(node, relation_name)
  if node.respond_to?(:eq) && node.respond_to?(:left) && node.left.respond_to?(:relation)
    node.left.relation.name == relation_name ? [node] : []
  elsif node.respond_to?(:children)
    node.children.flat_map { |c| collect_where_nodes(c, relation_name) }
  else
    []
  end
end

rel = Foo.where(:foo_field => 123).joins(:bars).where(:bars => { :xyz => 123 })

nodes = collect_where_nodes(rel.where_clause.ast, "bars")

However, the results from this function are Arel::Nodes, not a simplified hash like your original question asks for. Simple conditions could be mapped back to a hash, but this doesn't work for all cases (ranges, for example, would need more special handling):

hash = nodes.reduce({}) { |h, n| h.merge(n.left.name => n.right.value) }