Ransack Documentation

website·Indexed 30 days ago

https://activerecord-hackery.github.io/ransack/

A Rails library for adding advanced searching and filtering to ActiveRecord models. The documentation covers simple and advanced search modes, predicates, sorting, polymorphic search, and integration with PostgreSQL-specific types like JSONB and arrays.

Tokens
18.1K
Snippets
105
Records
125
Agent score
75%

What's inside Ransack

  1. Overview of Ransack for Rails searching

    Ransack is a tool for adding searching capabilities to Ruby on Rails applications using standard Ruby and ERB, without requiring additional infrastructure like ElasticSearch or Algolia. It is supported for Rails 8.1, 8.0, and 7.2 on Ruby 3.2 and later.
  2. Understand the Ransack search pipeline stages

    A search operation in Ransack proceeds through four distinct stages, each located within lib/ransack/:

    1. Parse (search.rb, nodes/): Converts the params hash into a tree of Grouping, Condition, Attribute, Value, and Sort nodes. This stage is database-agnostic.
    2. Resolve (context.rb, active_record/context.rb): Maps attribute names (e.g., articles_title) to tables, columns, and the necessary joins.
    3. Build (visitor.rb, predicate.rb, constants.rb): Traverses the node tree to generate Arel predicates and orderings.
    4. Integrate (active_record/): Handles all Active Record interactions, including class methods added to models, join machinery, and the Context that evaluates the search into a relation.
  3. Sort by virtual fields using model scopes

    To sort by a complex value or SQL function (e.g., REVERSE(name)), define scopes in your model that follow the naming convention sort_by_[virtual_field_name]_[direction]. You can then reference the virtual field name in sort_link.
    # In the Model
    class Person < ActiveRecord::Base
      scope :sort_by_reverse_name_asc, lambda { order("REVERSE(name) ASC") }
      scope :sort_by_reverse_name_desc, lambda { order("REVERSE(name) DESC") }
    end
    
    # In the View
    <%= sort_link(@q, :reverse_name) %>
  4. Construct search and sort URLs for Ransack

    Ransack expects search parameters as a hash. In a URL, this is represented using bracket notation: hash_name[key]=value.

    • hash_name: The parameter defined in the controller (commonly q).
    • key: A combination of the attribute name and the search predicate (e.g., first_name_cont).
    • value: The search term.
    • s: The sorting parameter, which must be nested inside the search hash (e.g., q[s]=created_at+desc).
    /users.json?q[first_name_cont]=pete&q[last_name_cont]=jack&q[s]=created_at+desc
  5. Install the bleeding-edge version of Ransack

    To use the latest updates from the main branch that have not yet been published to RubyGems, use the following Gemfile configuration:
    gem 'ransack', :github => 'activerecord-hackery/ransack', :branch => 'main'
  6. Migrate from Polyamorous to Ransack::ActiveRecord in 6.0

    The Polyamorous namespace and its require path have been removed. The Active Record integration has moved to Ransack::ActiveRecord.

    Key Mapping Changes:

    • Ransack::Adapters::ActiveRecord::Base $ ightarrow$ Ransack::ActiveRecord::Base (Deprecated in 6.0, removed in 7.0).
    • Polyamorous::Join $ ightarrow$ Ransack::ActiveRecord::Join.
    • Polyamorous::InnerJoin $ ightarrow$ Arel::Nodes::InnerJoin.
    • Polyamorous::OuterJoin $ ightarrow$ Arel::Nodes::OuterJoin.
    • Ransack::Context.for_class and for_object are replaced by Ransack::Context.register.
  7. Extend Arel to create complex custom predicates

    If the required SQL logic is not available in Arel's standard predicates, you can monkey-patch Arel::Predications to add a new method and then map a Ransack predicate to that new Arel method.
    # config/initializers/ransack.rb
    
    module Arel
      module Predications
        def gteq_or_null(other)
          left = gteq(other)
          right = eq(nil)
          left.or(right)
        end
      end
    end
    
    Ransack.configure do |config|
      config.add_predicate 'gteq_or_null', arel_predicate: 'gteq_or_null'
    end
  8. Search across ActiveRecord associations with Ransack

    Ransack allows searching for objects based on attributes of associated models in has_many and belongs_to relationships. To search an association, use the association name followed by the attribute name and a Ransack predicate (e.g., department_title_cont to search for a department's title containing a specific string).
    # Models
    class Employee < ActiveRecord::Base
      belongs_to :supervisor
    end
    
    class Department < ActiveRecord::Base
      has_many :supervisors
    end
    
    class Supervisor < ActiveRecord::Base
      belongs_to :department
      has_many :employees
    end
    
    # Controller
    def index
      @q = Supervisor.ransack(params[:q])
      @supervisors = @q.result.includes(:department, :employees)
    end
    
    # View (search_form_for)
    <%= search_form_for @q do |f| %>
      # Search Supervisor's own last name
      <%= f.search_field :last_name_cont %>
    
      # Search associated Department's title
      <%= f.search_field :department_title_cont %>
    
      # Search associated Employees' first OR last name
      <%= f.search_field :employees_first_name_or_employees_last_name_cont %>
    <% end %>
  9. Search PostgreSQL JSONB columns using the contains operator

    To perform searches on any key within a JSONB column using the PostgreSQL contains operator (@>), you must first install the ActiveRecordExtended gem to provide the contains arel predicate. Then, define a custom predicate in the Ransack configuration to handle JSON parsing of the input value.
    # config/initializers/ransack.rb
    Ransack.configure do |config|
      config.add_predicate 'jcont', arel_predicate: 'contains', formatter: proc { |v| JSON.parse(v) }
    end
    
    # Usage: Find entries where the 'data' JSONB column contains the specified key-value pair
    Person.ransack(data_jcont: '{"group": "experts"}').result.to_sql
    # Generates: SELECT "persons".* FROM "persons" WHERE "persons"."data" @> '"{\"group\": \"experts\"}"'
  10. Publish a Ransack release to RubyGems

    Ransack uses an automated workflow where publishing a GitHub Release triggers the build and push to RubyGems via Trusted Publishing.

    Release Workflow Steps:

    1. Merge all feature/fix pull requests into main.
    2. Merge a version pull request that updates lib/ransack/version.rb (this should be the final commit before tagging).
    3. Create a GitHub Release:
      • Tag: vX.Y.Z (e.g., v5.1.0), target main.
      • Release title: X.Y.Z (e.g., 5.1.0).
      • Notes: Use 'Generate release notes' and organize by breaking changes, features, and bug fixes.
      • Latest release: Ensure 'Set as the latest release' is checked.
    4. Publish the release to trigger the GitHub Action. The workflow verifies that the tag matches Ransack::VERSION before pushing to RubyGems.
    module Ransack  
      VERSION = '5.1.0'  
    end
  11. Search polymorphic associations with namespaced models

    When the target model of a polymorphic association is within a namespace (module), use a quoted string in the search key containing the standard Ruby module notation (e.g., Module::Class).
    # Search for locations where the associated Residences::House has a number equal to 100
    Location.ransack('locatable_of_Residences::House_type_number_eq' => 100).result
  12. Search Active Record enum attributes by label

    Ransack allows searching Active Record enum attributes using their human-readable labels instead of their underlying integer values. Ransack automatically casts the label to the corresponding value before building the query.
    class Person < ApplicationRecord
      enum :temperament, { sanguine: 1, choleric: 2, melancholic: 3, phlegmatic: 4 }
    end
    
    # Search by single label
    Person.ransack(temperament_eq: 'choleric').result.to_sql
    # ... WHERE "people"."temperament" = 2
    
    # Search by multiple labels
    Person.ransack(temperament_in: ['sanguine', 'choleric']).result.to_sql
    # ... WHERE "people"."temperament" IN (1, 2)