Security
- Includes the
sort_link/sort_urlfix released as 6.0.1. Request params were passed tourl_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 dropsurl_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_predicatetakesarel_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 patchingArel::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/_allforms, and must return anArel::Nodes::Node(anything else raisesArgumentError). The result is normalized like a built-in predicate's, and works withnull_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
resulttwice, or putting two negative conditions on the same collection, no longer raisesNoMethodError(undefined method 'table' for nil). This was a regression in 6.0.0:search.resultfollowed bysearch.result.order_values, a pair of conditions likearticles_title_not_eqplusarticles_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_scopestill 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
- the type column of a polymorphic
-
A join something else in the search reads is kept. A sort on the same collection (
s: 'articles_title asc', e.g. from asort_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
resultand 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
%0Ain a condition or sort key raisedNoMethodError, andnotable_of_Person_type%0Azzz_name_eqwas searched asnotable_of_Person_type_name_eq. Such a key is now an unknown key like any other. — #1764 -
Sort link labels are thread-safe.
Translatekept the name it was translating on the module, so two requests rendering associationsort_links at once could swap labels or raiseI18n::MissingInterpolationArgument. — #1765 -
A
Timepassed to a scope reaches the scope whole. It was splatted throughTime#to_aand raisedArgumentError. 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 existswas raised because the schema cache didn't list them; Active Record queries them fine. — #1779, fixes #1770 -
An association path in
ransackable_attributesis followed. Withperson_nameallowlisted on Article,person_name_contwas bound asarticles.person_nameand raisedno such column. — #1780, fixes #1774 -
An Array value is spread into one value per element. A group of check boxes for
name_eqwas compared with the stringified array,salary_eq: [1, 2]was silently dropped, and the same happened inside one value of thec/a/p/vAPI. 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 underransackand raisesInvalidSearchErrorunderransack!, as thec:form already did, instead of matching the array's string form. — #1782, fixes #1771 and #1772 -
cont,start,endandmatcheskeep the term as text on a non-text column. The value was cast to the column's type before the pattern was built, soid_or_name_cont: "Alpha"becameid LIKE '%0%'and returned unrelated records,id_cont: "021"searched%21%,id_i_contraisedNoMethodError, 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.rbnow requiresactive_supportbefore its core extensions, andbuild_correlated_subquerybuilds itsSelectManagerfrom 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_endfamily, thelength_*predicates, and theirnot_,_anyand_allforms. Predicate choosers in ActiveAdmin,simple_formand similar no longer show raw keys likeI_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-mainjob 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