Filtering

The Filtering module allows you to apply filters to your collections based on the attributes you define and the values they have been given, e.g., from a with_params call.

This feature is designed for use with the Query module, allowing you to define and apply filters to attributes in your collection model, but can be used independently as well if you prefer to create a custom form.

Usage

To use the Filtering module, include it in your collection class and define the attributes you want to filter. The module applies filters based on the value of each given attribute to the collection.

class Collection < Katalyst::Tables::Collection::Base
  include Katalyst::Tables::Collection::Filtering
  
  attribute :id, :integer, multiple: true
  attribute :search, :search, scope: :table_search
  attribute :name, :string
  attribute :active, :boolean
  attribute :updated, :boolean, scope: :updated
  attribute :category, :enum
  attribute :"parent.name", :string
  attribute :"parent.active", :boolean
  attribute :"parent.updated", :boolean
  attribute :"parent.id", :integer, multiple: true
  attribute :"parent.role", :enum
end

To apply filters, use the with_params method to pass the query parameters, and then call the apply method with the initial scope (e.g., Resource.all).

collection.with_params(params).apply(Resource)
scope = collection.with_params(active: true).apply(Resource)
# => Resource.where(active: true)

You can implement derived booleans which do not exist in the model too. When filtering on a derived attribute, Filtering will look for a scope specified on the attribute and pass the value of the boolean attribute:

# model scope
scope :updated, ->(updated) { updated ? where("created_at != updated_at") : where("created_at = updated_at") }

# collection attribute
attribute :updated, :boolean, scope: :updated

# controller usage
collection.with_params(updated: true).apply(Resource).items
# => Resource.where("created_at != updated_at")

Attributes that have a default array value can accept multiple input values:

 scope = collection.with_params(category: "report").apply(Resource)
 # => Resource.where(category: :report)
 
scope = collection.with_params(category: ["article", "report"]).apply(Resource)
 # => Resource.where(category: %w[article report])

String attributes use like matching:

scope = collection.with_params(name: "Aaron").apply(Nested::Child)
# => Nested::Child.where("name LIKE ?", "%Aaron%")

String filtering with associations:

 scope = collection.with_params("parent.name": "test").apply(Nested::Child)
 # => Nested::Child.joins(:parent).where("parents.name LIKE ?", "%test%")

Associations

scope = collection.with_params("parent.role": "teacher").apply(Nested::Child)
# => Nested::Child.joins(:parent).merge(Parent.where(role: :teacher))

scope = collection.with_params("parent.active": true).apply(Nested::Child)
# => Nested::Child.joins(:parent).merge(Parent.where(active: true))

scope = collection.with_params("parent.id": "15").apply(Nested::Child)
# => Nested::Child.joins(:parent).where("parents.id = ?", 15)

scope = collection.with_params("parent.id": [1, 2, 3]).apply(Nested::Child)
# => Nested::Child.joins(:parent).where("parents.id IN ?", [1, 2, 3])

scope = collection.with_params("parent.name": "test").apply(Nested::Child)
# => Nested::Child.joins(:parent).where("parents.name LIKE ?", "%test%")