Skip to content

A fully featured filtering gem designed for Ruby on Rails apps with Postgres databases

License

Notifications You must be signed in to change notification settings

ibm-skills-network/sn_filterable

Repository files navigation

SnFilterable

Welcome to the Skills Network Filterable gem!

This gem provides a method for developers to quickly implement a customizable search and filter for their data with live-reloading.

Live examples of the gem's use can be viewed at Skills Network's Author Workbench, primarily under the organizations tab

Requirements

There are a couple key requirements for your app to be compatible with this gem:

  1. You need to have AlpineJS loaded into the page where you plan to use SnFilterable
  2. Your app needs to be running TailwindCSS

Installation

Add this line to your application's Gemfile:

gem "sn_filterable"

And then execute:

bundle install
Make the following adjustments to your codebase
  1. Add the necessary translations and customize as desired
# en.yml
en:
    # Other translations
    shared:
        filterable:
        view_filter_button: "View filters"
        results_per_page: "Results per page"
        clear_all: "Clear all"
        pagination:
            previous_page: "Previous"
            next_page: "Next"
  1. Require the necessary JavaScript (dependent on AlpineJS being included with your App)
// application.js converted to application.js.erb

// other imports
<%= SnFilterable.load_js %>

If your application does not allow for use of .js.erb files the JavaScript can be loaded by adding the following to your layout:

<%= javascript_tag nonce: true do %>
  <%= SnFilterable.load_js.html_safe %>
<% end %>
  1. Configure your app's Tailwind to scan the gem
// tailwind.config.js
const execSync = require('child_process').execSync;
const output = execSync('bundle show sn_filterable', { encoding: 'utf-8' });

module.exports = {
  // other config settings
  content: [
    // other content
    output.trim() + '/app/**/*.{erb,rb}'
  ]
  // other config settings
};

Usage

The MainComponent: Search Bar and sidebar

The MainComponent is what is demo'd in the introduction. It consists of the search bar and a sidebar for filters.

If you only wish to use the Search bar an optional show_sidebar: false parameter can be passed to SnFilterable::MainComponent in the view.

There are three components which work to provide the text search functionality:

  1. Filters in the given model:
# model.rb
class Model < ApplicationRecord
    include SnFilterable::Filterable

    FILTER_SCOPE_MAPPINGS = {
        "search_name": :filter_by_name
        # 'search_name' will be referenced from the view
    }.freeze


    SORT_SCOPE_MAPPINGS = {
        "sort_name": :sort_by_name
        # 'sort_name' will be referenced from the controller
    }.freeze

    scope :filter_by_name, ->(search) { where(SnFilterable::WhereHelper.contains("name", search)) }
    scope :sort_by_name, -> { order :name }
    # 'name' is a string column defined on the Model

    # Model code...
end
  1. Setting up the controller
  • While :default_sort is an optional parameter it is recommended
# models_controller.rb
@search = Model.filter(params:, default_sort: ["sort_name", :asc].freeze)
@models = @search.items
  1. Rendering the ViewComponent
<%= render SnFilterable::MainComponent.new(frame_id: "some_unique_id", filtered: @search, filters: [], search_filter_name: "search_name") do %>
    <% @models.each do |model| %>
        <%= model.name %>
    <% end %>
    <%= filtered_paginate @search %> # Kaminari Gem Pagination
<% end %>

The MainComponent: Adding filters to the sidebar

Adding filters to the sidebar requires changes to two files though we recommend storing the data across three files and will demsontrate as such.

  1. Add filters to Model
# app/models/model.rb
class Model < ApplicationRecord
    # inclusion statement from introduction

    FILTER_SCOPE_MAPPINGS = {
        # other filter scopes...
        "model_type_filter": :filter_by_type
    }.freeze
    # 'model_type_filter' will be referenced in step 2

    ARRAY_FILTER_SCOPES = %i[model_type_filter].freeze
    # safelist of all filters we will be rendering in our sidebar
    
    scope :filter_by_type, ->(model_type_input) { where(model_type: model_type_input) }
    # where 'model_type' is an attribute defined on Model
end
  1. Create filter options
# app/models/filter.rb
# We store in a filter.rb model, but you can store as desired
class Filter
  MODEL_FILTERS = [
    {
      multi: true,
      title: "Type",
      filter_name: "model_type_filter",
      filters: %w(Special Normal).map { |type| { name: type, value: type } }
      # Allows us to filter between 'Special' and 'Normal' model types
      # Note we recommend storing the %w(Special Normal) array at a central location for easier validation and manipulation
    }
  ].freeze
end
  1. Render as part of our MainComponent
<!-- Notice the addition of a non-empty 'filters' argument which references step 2 -->
<%= render SnFilterable::MainComponent.new(frame_id: "some_unique_id", filtered: @search, filters: Filter::MODEL_FILTERS, search_filter_name: "search_name") do %>
    <!-- display code.. -->
<% end %>

Testing / Development

This gem using RSpec for testing. Tests can be running locally by first setting up the dummy database/app as follows:

docker compose up -d
cd spec/dummy
rails db:create
rails db:schema:load

Now the test suite can be run from the project root using

bundle exec rspec

Using a Development Version

Your changes can be tested manually by making one of the following additions to an App's Gemfile:

For using a local version:

gem "sn_filterable", path: "path/to/gem/sn_filterable"

For using a GitHub branch:

gem "sn_filterable", git: "https://github.com/ibm-skills-network/sn_filterable.git", branch: "defaults_to_main"

Contributing

Once you have made your updates to the codebase do the following to ensure a smooth merge:

  1. Update lib/sn_filterable/version.rb to follow Semantic Versioning

  2. Run bundle to update the Gemfile.lock with your new version

Bug reports and pull requests are welcome on GitHub.

License

The gem is available as open source under the terms of the MIT License.