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:
- You need to have AlpineJS loaded into the page where you plan to use SnFilterable
- 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
- 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"
- 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 %>
- 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:
- 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
- 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
- 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.
- 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
- 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
- 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:
-
Update
lib/sn_filterable/version.rb
to follow Semantic Versioning -
Run
bundle
to update theGemfile.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.