Skip to content

v6.1.0

Latest

Choose a tag to compare

@scarroll32 scarroll32 released this 08 Nov 18:31
15559bd

Security

  • Includes the sort_link / sort_url fix released as 6.0.1. Request params were passed to url_for, so a crafted query string such as ?script_name=javascript:... made sort links run script in the app's origin or point to another site (reflected XSS and open redirect). Ransack now drops url_for's URL options (script_name, host, protocol, only_path, ...) from the params it carries into the link. GHSA-58rx-96gf-w92x.

Compatibility

  • Ruby 3.3 or later and Rails 8.1 or later are required. Ruby 3.2 reached end of life on 2026-03-31, Rails 7.2 on 2026-08-09 and Rails 8.0 on 2026-11-07; none of them receives security fixes. Nothing else changes, so on a supported Ruby and Rails the upgrade needs no code changes. On Ruby 3.2 or Rails 8.0 or earlier, stay on 6.0. — #1756, #1786

Features

  • add_predicate takes arel_node:, a callable that receives the attribute and the cast value and returns a finished Arel node, so a custom predicate can combine conditions without monkey patching Arel::Predications:

    config.add_predicate 'gteq_or_null',
      arel_node: ->(attr, val) { attr.gteq(val).or(attr.eq(nil)) }

    It can't be combined with arel_predicate, has no _any / _all forms, and must return an Arel::Nodes::Node (anything else raises ArgumentError). The result is normalized like a built-in predicate's, and works with null_sentinel. — #1789 by @agrberg

Bug fixes

Negative predicates on a collection (articles_title_not_eq, tags_name_not_in, notes_note_not_cont, ...) are built as a correlated subquery. 6.1 fixes how that subquery is built and which joins come with it:

  • Calling result twice, or putting two negative conditions on the same collection, no longer raises NoMethodError (undefined method 'table' for nil). This was a regression in 6.0.0: search.result followed by search.result.order_values, a pair of conditions like articles_title_not_eq plus articles_body_not_cont, or an _or_ attribute all failed. — #1746, fixes #1745

  • The subquery reaches the same records the association does. It used to keep only the foreign-key correlation and drop the rest of the join, so it counted rows the association doesn't reach, and returned wrong records without raising:

    • the type column of a polymorphic has_many ... as:: a note on another model with the same id excluded the record
    • the target's default scope: a comment hidden by default_scope still excluded the record
    • the STI type column and the association's own scope
    • an association with a primary_key: option produced invalid SQL; it's now tested on that column

    — #1751, fixes #1747

  • A join something else in the search reads is kept. A sort on the same collection (s: 'articles_title asc', e.g. from a sort_link), or a condition or sort on an association nested under it (articles_comments_body_eq), referenced a join the negative condition had dropped (no such column). The join now stays alongside the subquery. — #1752, fixes #1749

  • A search can be changed after result and evaluated again. Evaluating a search no longer changes it. A sort or condition added afterwards used to reference a dropped join, or an alias (articles_people) the query didn't have. The joins come from the search as it stands when it is evaluated, so clearing a sort or condition also drops its join instead of leaving one behind to repeat records. — #1753, fixes #1750

  • Every collection on the model gets the subquery. It was only built when the attribute name started with the collection's table name, so an STI subclass (story_articles_title_not_eq), a scoped association (published_articles_title_not_eq) or one named differently from its table fell back to a join with a row-level !=, and returned records that do have a matching row. — #1783, fixes #1773

Joins a search adds now line up with the joins the relation already has:

  • A condition nested under an eager-loaded association no longer produces invalid SQL. Article.eager_load(:author).ransack(author_profile_id_not_null: true) joined the profile before the author it hangs off. This was a regression in 6.0.0. — #1768, fixes #1767
  • A direct association and a nested one to the same table stay apart. Comment.ransack(person_name_eq: ..., article_person_name_eq: ...) returned nothing when the nested condition came first, because both conditions were bound to the same join. — #1781, fixes #1775
  • Joins added to a result keep the search on its own joins. result.joins(:target_person) took the table name the search's join expected, so the search's conditions silently filtered the wrong join, and a string join produced invalid SQL. — #1784, fixes #1776

Malformed search params raise Ransack::InvalidSearchError from ransack as well as ransack!. A hand-edited query string such as ?q[g]=x, ?q[c][0]=x or ?q[s][0]=name used to raise ArgumentError, NoMethodError or TypeError from deep inside the node that tripped over it, and an array where a single value belongs (?q[s][0][dir][]=desc, ?q[c][0][p][]=eq) was silently dropped or read as ascending, so rescue_from Ransack::InvalidSearchError turned it into a 500 rather than a 400. InvalidSearchError is an ArgumentError, so existing rescues still work. Also: a hash sort without a direction (s: [{ name: 'title' }]) no longer raises and sorts ascending, and dir: :desc is read instead of falling back to ascending. — #1763

  • A key with a newline in it no longer crashes a search, or searches something else. Key patterns matched line by line, so %0A in a condition or sort key raised NoMethodError, and notable_of_Person_type%0Azzz_name_eq was searched as notable_of_Person_type_name_eq. Such a key is now an unknown key like any other. — #1764

  • Sort link labels are thread-safe. Translate kept the name it was translating on the module, so two requests rendering association sort_links at once could swap labels or raise I18n::MissingInterpolationArgument. — #1765

  • A Time passed to a scope reaches the scope whole. It was splatted through Time#to_a and raised ArgumentError. Only an Array is spread into arguments now. — #1778, fixes #1769

  • Temporary tables, and tables whose names differ only in case, can be searched. No table named X exists was raised because the schema cache didn't list them; Active Record queries them fine. — #1779, fixes #1770

  • An association path in ransackable_attributes is followed. With person_name allowlisted on Article, person_name_cont was bound as articles.person_name and raised no such column. — #1780, fixes #1774

  • An Array value is spread into one value per element. A group of check boxes for name_eq was compared with the stringified array, salary_eq: [1, 2] was silently dropped, and the same happened inside one value of the c/a/p/v API. Predicates that take a list (in, not_in, *_any, *_all) now get each element, and a one-element array is searched as its element. A single-value predicate given several values is now dropped under ransack and raises InvalidSearchError under ransack!, as the c: form already did, instead of matching the array's string form. — #1782, fixes #1771 and #1772

  • cont, start, end and matches keep the term as text on a non-text column. The value was cast to the column's type before the pattern was built, so id_or_name_cont: "Alpha" became id LIKE '%0%' and returned unrelated records, id_cont: "021" searched %21%, id_i_cont raised NoMethodError, and a date or boolean column in an _or_ condition dropped the condition or matched every row. Other predicates still cast (salary_eq: "Alpha" is = 0). — #1794, fixes #1793

  • Ransack loads on Rails main. lib/ransack.rb now requires active_support before its core extensions, and build_correlated_subquery builds its SelectManager from the model's Arel table instead of the class. — #1792 and #1795 by @thomasklemm, #1796

Performance

  • Building a search is 1.2–2.6× faster, with 50–80% fewer allocations (1.1–2.0× including SQL generation). Each allowlist is asked for once per search, predicates are looked up by name instead of scanned, and aliases and attribute names are resolved once per key. No behaviour change. — #1766

Translations

  • The 35 predicates missing from every locale are translated in all 28 locales: the case-insensitive i_cont / i_start / i_end family, the length_* predicates, and their not_, _any and _all forms. Predicate choosers in ActiveAdmin, simple_form and similar no longer show raw keys like I_cont. — #1744 by @hasghari

Documentation

  • The docs site moves to the VitePress theme for Jekyll, with a new home page, Installation, Authorization, Aliases, Scopes and Community pages split out of the old introduction and Other notes, and every old URL kept as a page or a redirect. The README is shorter and points into the site. — #1762

  • A Security page covers how to report a vulnerability and how maintainers handle one; SECURITY.md, the README and the community page point to it. — #1790

  • The docs site uses the ActiveRecord Hackery logo and its reds. — #1791

Project

  • Restored the Open Collective support link in the README and on the docs home page. — #1748
  • Added issue and pull request templates. — #1754
  • CI tests Rails 8.1 across Ruby 4.0, 3.4 and 3.3 on SQLite, MySQL, Trilogy and PostgreSQL in 5 test jobs, down from 24. The nightly job is replaced by a rails-main job on every PR and push, which is allowed to fail. — #1756, #1759, #1786, #1798, #1800
  • CI actions run on Node 24 (actions/checkout@v7, peaceiris/actions-gh-pages@v4). — #1787
  • 83 specs follow the code examples in 78 closed issues, so a regression of reported behaviour fails the suite. — #1777

Full changelog: v6.0.0...v6.1.0