Skip to content

feat: Order results by a field of their nested children with order_by_child - #7

Draft
njakobsen wants to merge 2 commits into
masterfrom
nested-sort
Draft

njakobsen wants to merge 2 commits into
masterfrom
nested-sort

Conversation

@njakobsen

@njakobsen njakobsen commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Adds order_by_child, which sorts parents by a field of their nested children. It takes the same block as with_child:

Sunspot.search(Project) do
  order_by_child :milestones, :started_at, :desc do
    with :name, "design"
  end
end

The sort is a {!parent} block join whose score is the children's value, over {!bool} of the block's restrictions, a filter that the field has a value, and {!func} of the field. A parent with no matching child that has a value comes last in either direction. Several children combine by the smallest value ascending and the largest descending, unless :score says otherwise.

  • Needs Solr 8 or later; the README says so. Solr 5.3.1 (bundled) and 6.6.6 reject the sort, and Solr 7.7.3 runs it but doesn't tie times before 1970 and gets a descending :score => :min sort wrong. Its specs are skipped before Solr 8 with skip_before_solr.
  • Only single-value numeric and time fields can be sorted by; a date range field raises ArgumentError. Solr compares the scores as floats, so values very close together can tie, and it reads a negative score as zero, so times before 1970 tie with each other and negative numbers with zero. A later order_by breaks a tie.

Testing

Integration and API specs for the sort, including a parent whose matching children lack the field. Locally, in both update formats, the 418 integration examples pass on solr:9.10.1 (5 pending) and on the bundled Solr 5.3.1 (12 pending: the 10 order_by_child examples and 2 that need Solr 8). The nested documents specs also pass, with the same skips, on Solr 6.6.6 and 7.7.3.

These counts include upstream master. stolo_connect's full culturecode suite, and its referral specs on all 31 clients, also pass against this branch.

Commits

Two commits on master: the feature, with its review fixes folded in, and a spec cleanup that replaces the nested documents spec's own skip_before_solr_8 with skip_before_solr. The tree is identical to the previous tip, a94d077e.

🤖 Generated with Claude Code

@njakobsen
njakobsen changed the base branch from ci-production-solr to master October 21, 2026 16:51
njakobsen added a commit that referenced this pull request Oct 23, 2026
Root cause: #7 raised the stated minimum for `order_by_child` from Solr 7 to 8 in the README and the specs, but its doc comment in `DSL::FieldQuery` still said Solr 7, where some parents sort differently.

The comment now says Solr 8. The nested documents spec's own `skip_before_solr_8`, which read the Solr version on every call, is replaced by `skip_before_solr 8`, which reads the memoized `IntegrationHelper.solr_version`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TAYgm5SknHTiRNTgGA6LBY
@njakobsen
njakobsen force-pushed the nested-sort branch 2 times, most recently from c1c0518 to 34378c3 Compare October 24, 2026 18:04
…y_child`

Issue: nested documents could filter parents by their children (`with_child`, `without_child`) but not sort by them, so "projects by when their design milestone started" wasn't expressible. Sunspot has no precedent for sorting by children, so this follows Solr's: sort by a `{!parent}` block join query whose score is the children's value.

`order_by_child :milestones, :started_at, :desc do … end` takes the same block as `with_child`, and adds a `Sort::ChildSort`. The sort is `exists(query($child_sort_N)) desc, query($child_sort_N) <direction>`, where `child_sort_N` is `{!parent … score=<min|max|avg|total>}` over `{!bool}` of the block's restrictions, a filter that the field has a value, and `{!func}` of the field (`ms()` for a time). A parent with no matching child that has a value comes last in either direction. Several children combine by the smallest value ascending and the largest descending, unless `:score` says otherwise. `SortComposite` names each child sort's parameters by its position. `DSL::Scope#build_block_join` is shared with `with_child`.

Only single-value numeric and time fields can be sorted by; a date range field raises `ArgumentError`. Solr compares the scores as floats, so values too close together can tie, and reads a negative score as zero, so times before 1970 tie with each other and negative numbers with zero. The README and the doc comment say so.

It needs Solr 8 or later, which the README and doc comment also say. Solr 5.3.1, which `sunspot_solr` bundles, and 6.6.6 reject the sort, and 7.7.3 doesn't tie times before 1970 and gets a descending `:score => :min` sort wrong. `IntegrationHelper#skip_before_solr` skips its integration specs before Solr 8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The nested documents spec defined its own `skip_before_solr_8`, which asked Solr for its version on every call. It now calls `IntegrationHelper#skip_before_solr`, which reads the memoized `IntegrationHelper.solr_version`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant