How to Search Polymorphic Associations Using Ransack
To search polymorphic associations using Ransack, use the attribute naming pattern association_of_Model_type_column_predicate (e.g., notable_of_Person_type_name_eq), which automatically generates SQL conditions for both the foreign-key and type columns.
Searching across polymorphic belongs_to relationships in Ruby on Rails requires special handling because the database stores the association across two columns. The Ransack gem (activerecord-hackery/ransack) provides a specific naming convention that allows you to search polymorphic associations using standard search predicates while automatically managing the required type constraints.
Understanding the Polymorphic Search Syntax
Ransack treats polymorphic associations similarly to standard associations, but requires a specific naming pattern to identify the concrete class you want to target. The pattern follows this structure:
<polymorphic_association>_of_<TargetModel>_type_<column>_<predicate>
For example, if you have a Note model with belongs_to :notable, polymorphic: true, and you want to search for notes where the notable Person's name equals "Ernie", you would use:
notable_of_Person_type_name_eq
This pattern breaks down into:
<polymorphic_association>— the name of thebelongs_toassociation (e.g.,notable)of_<TargetModel>_type— specifies the concrete model class to query (e.g.,of_Person_type)<column>— the attribute on the target model (e.g.,name)<predicate>— any standard Ransack predicate such aseq,cont, orlt
How Ransack Handles Polymorphic Joins
When you use this naming convention, Ransack automatically generates a join that includes both necessary conditions: matching the foreign key (notable_id) and restricting the type column (notable_type) to the specified class name.
According to the source code in lib/ransack/adapters/active_record/context.rb, the method build_correlated_subquery (lines 76-90) detects polymorphic associations by identifying an array of correlated keys. When Ransack encounters this array, it forces the type key to equal the model's class name, ensuring the generated SQL includes the type constraint alongside the foreign-key match.
The resulting SQL structure includes both conditions:
WHERE notes.notable_id = people.id
AND notes.notable_type = 'Person'
Searching Across Polymorphic Associations: Code Examples
Basic Search on a Single Concrete Type
To find all notes whose notable Person has a specific name:
search = Note.ransack(notable_of_Person_type_name_eq: 'Ernie')
notes = search.result
This generates SQL similar to:
SELECT "notes".*
FROM "notes"
INNER JOIN "people" ON "people"."id" = "notes"."notable_id"
WHERE "notes"."notable_type" = 'Person'
AND "people"."name" = 'Ernie'
Searching Across Multiple Polymorphic Types
You can combine conditions across different concrete types using the _or_ combinator. This example searches for notes where the notable is either a Person named "Ernie" or an Article titled "Ernie":
search = Note.ransack(
notable_of_Person_type_name_or_notable_of_Article_type_title_eq: 'Ernie'
)
notes = search.result
The generated SQL uses LEFT OUTER JOINs for both tables and applies the appropriate type constraints:
SELECT "notes".*
FROM "notes"
LEFT OUTER JOIN "people" ON "people"."id" = "notes"."notable_id"
LEFT OUTER JOIN "articles" ON "articles"."id" = "notes"."notable_id"
WHERE ("notes"."notable_type" = 'Person' AND "people"."name" = 'Ernie')
OR ("notes"."notable_type" = 'Article' AND "articles"."title" = 'Ernie')
Using Array Predicates
You can use predicates that accept arrays, such as in:
# Find notes whose notable Person's name is either "Ernie" or "Bert"
search = Note.ransack(notable_of_Person_type_name_in: %w[Ernie Bert])
notes = search.result
Custom Predicates with Polymorphic Associations
Custom predicates work seamlessly with polymorphic attributes. First define the predicate:
Ransack.configure do |config|
config.add_predicate 'starts_with',
arel_predicate: 'matches',
formatter: ->(v) { "#{v}%" },
validator: ->(v) { v.present? }
end
Then use it with the polymorphic pattern:
search = Note.ransack(notable_of_Article_type_title_starts_with: 'Rails')
notes = search.result
Key Implementation Files in Ransack
Understanding where this logic lives helps when debugging complex queries:
lib/ransack/adapters/active_record/context.rb— Contains thebuild_correlated_subquerymethod (lines 76-90) that handles the two-part correlated key logic for polymorphic joins, forcing the type condition when an array of keys is detected.lib/ransack/search.rb— Orchestrates the parsing of attribute strings and builds theNodes::Conditionobjects that represent polymorphic search criteria.lib/ransack/nodes/condition.rb— Represents the final condition objects that include the full polymorphic attribute name.spec/ransack/search_spec.rb(lines 9-31) — Contains test cases demonstrating the attribute naming convention, including examples likenotable_of_Person_type_name_eqand cross-type OR conditions.spec/support/schema.rb(lines 57-68) — Defines the test schema withNotemodels using polymorphicbelongs_to :notable.
Summary
- Use the pattern
<association>_of_<Model>_type_<column>_<predicate>to search polymorphic associations in Ransack. - Ransack automatically generates both the foreign-key and type-column conditions required for correct polymorphic joins.
- The core logic resides in
Ransack::Adapters::ActiveRecord::Context#build_correlated_subquery, which detects polymorphic associations through correlated key arrays. - You can search across multiple concrete types in a single query using the
_or_combinator in the attribute name. - All standard Ransack predicates, including custom ones, work with the polymorphic naming convention.
Frequently Asked Questions
What is the exact syntax for searching polymorphic associations in Ransack?
The syntax follows the pattern association_of_Model_type_column_predicate. For example, if you have a Comment model with belongs_to :commentable, polymorphic: true and want to find comments on posts with a specific title, use commentable_of_Post_type_title_eq. Ransack parses this string to identify the association, target model, column, and predicate.
How does Ransack handle the type column in polymorphic searches?
Ransack automatically injects the type condition when it detects a polymorphic attribute pattern. In lib/ransack/adapters/active_record/context.rb, the build_correlated_subquery method checks for an array of correlated keys (foreign key and type key). When found, it forces the type key to equal the target model's class name, generating SQL like notable_type = 'Person' alongside the join condition.
Can I search across multiple polymorphic types in a single Ransack query?
Yes. Use the _or_ combinator between different type specifications. For example: notable_of_Person_type_name_or_notable_of_Company_type_name_eq. This creates a LEFT OUTER JOIN for each table and applies the appropriate type constraints in the WHERE clause, allowing you to match records from different concrete types in one search.
Do custom Ransack predicates work with polymorphic associations?
Yes. Any custom predicate defined via Ransack.configure works with polymorphic attributes using the standard naming pattern. Simply append your custom predicate to the end of the polymorphic attribute string, such as notable_of_Article_type_title_starts_with if you have defined a starts_with predicate.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →