Skip to content

Predicate Builder

Carlos Silva edited this page Jul 29, 2025 · 1 revision

This is just somewhat specific to PostgreSQL, but mostly an enhancement to Rails ActiveRecord::PredicateBuilder, whose primary purpose is to make .where and .where.not calls in the format for (attribute: value) correctly translate into several distinct Arel and SQL operations. Its sole purpose is to ease the process of writing queries, without the need to use Arel directly for everything.

The inspiration for these features, and some notable examples, is the handling of Array, Range, and ActiveRecord::Base (A model). Each one of them gives the developer exactly what they would expect from their respective operation. That said, here are a few more options that are now made available.

You can pick and choose the ones you want enabled/disabled using predicate_builder.enabled config.

Regexp

Translate the given Regexp into a bind param while identifying the proper infix operator to use based on the case-insensitive indicator.

Video.where(title: /(one|two)/)     # WHERE "videos"."title" ~ '(one|two)'
Video.where(title: /(one|two)/i)    # WHERE "videos"."title" ~* '(one|two)'

Enumerator::Lazy

The rare use of [].lazy, where we would usually have to call .force, but there are cases where we would like to support both lazy and non-lazy operations simultaneously. This also makes it safe to use due to predicate_builder.lazy_timeout and predicate_builder.lazy_limit, which limit the resources used.

Video.where(user_id: [1,2].lazy)    # WHERE "videos"."user_id" IN (1,2)

Arel::Attributes::Attribute

At first, this may seem unnecessary. However, when working with joins, this can be particularly helpful.

Video.joins(:tags).where(language: Tag.arel_table['language'])    # WHERE "videos"."language" = "tags"."language"

Another great advantage of this is the proper handling of array columns, which completely facilitated the operations of Belongs to Many features.

Video.where(tag_ids: Tag.arel_table['id'])          # WHERE "tags"."id" = ANY("videos"."tag_ids")
Tag.where(id: Video.arel_table['tag_ids'])          # WHERE "tags"."id" = ANY("videos"."tag_ids")
Video.where(tag_ids: User.arel_table['tag_ids'])    # WHERE "videos"."tag_ids" && "users"."tag_ids"

Array

This feature needs to be enabled via predicate_builder.handle_array_attributes because it may break the current state of your application if you have been using .where with array columns and any type of value (eg, .where(tag_ids: [1,2,3])).

The primary purpose of this feature is to more accurately convey the meaning of array and non-array values in these operations. It only takes place when .where is used against an array column.

Video.where(tag_ids: [1,2,3])    # WHERE "videos"."tag_ids" && '{1,2,3}'
Video.where(tag_ids: 1)          # WHERE 1 = ANY("videos"."tag_ids")
Video.where(tag_ids: [])         # WHERE CARDINALITY("videos"."tag_ids") = 0
Clone this wiki locally