-
Notifications
You must be signed in to change notification settings - Fork 1
Stabilizers
Here we'll go over the process of stabilizing filters so that they can be viewed, reused, or edited at a later time. We use the term "stabilization" because after a filter goes through the stabilization process, you are returned a string or an ID that will always and forever reference a specific filter in a specific state.
Stabilization is similar to serializing or encoding, in that a filter with a state $x
will always stabilize to an ID $y
, and an ID $y
will always resolve to a filter with state $x
. It is bi-directional.
Why do we call it stabilizing and not persisting? Persisting implies that the data will be stored somewhere, which is not necessarily true of all of Refine's stabilization drivers. The Database Stabilizer persists the data, but the URL Encoder does not.
Why don't we call it serializing? Serializing usually implies that all of the data is still present in the resulting value. That's not true of all of Refine's stabilization drivers either! The result of the URL Encoder contains all the data, but the Database Stabilizer returns a small pointer to a record in the database.
We landed on the term "stabilization" because it encompasses both concepts.
By default, the filters are URL Encoded stabilized. This enables us to pass a stable_id
to your controller and it is used in the apply_filter
method that is included in the FilterApplicationController
helper.
You may call from_stable_id
on a filter at any time. You'll need to pass in the Stabilizer that you want to use.
# Rehydrate a filter from a `stable_id`
filter = Stabilizers::UrlEncodedStabilizer.new.from_stable_id(id: params[:stable_id])
You can optionally send in an initial_query
when rehydrating the filter. This is useful if you're using cancancan
or pundit
and want to preserve tenancy
# Rehydrate a filter from a `stable_id`
filter = Stabilizers::UrlEncodedStabilizer.new.from_stable_id(id: params[:stable_id], initial_query: initial_query)
Once you have the filter object, you can call get_query
on the object to return the query object.
# Rehydrate a filter from a `stable_id`
filter = Stabilizers::UrlEncodedStabilizer.new.from_stable_id(id: params[:stable_id])
# Get the query - returns and Active Record::Relation object
filter.get_query
If you'd like some of your filters to be saved to you refine_stored_filters
table, you can "translate" from a URLEncoded id to a Database id. You may want to only save filters once the entire model has pass validation for example. Here is what that would look like:
def save_filter_and_return_id(id:, initial_query: nil)
# How to use (in controller) -> filter_id = save_filter_and_return_id(id: params[:stable_id], initial_query: scope)
# This method returns a primary key of the filter in your refine_stored_filters table which you can then add to your model
filter = Stabilizers::UrlEncodedStabilizer.new.from_stable_id(id: id, initial_query: initial_query)
Stabilizers::DatabaseStabilizer.new.to_stable_id(filter: filter)
end
We have provided 2 stabilizers out of the box for you. Each one does something a little bit different that makes sense in different situations. You can read more about them on their individual pages.
- Database - Persists the state to the database.
- UrlEncoded - Encodes the filter's state but doesn't persist it anywhere. Totally self-contained.
All stabilizers must implement the Stabilizer
interface, which enforces two methods: from_stable_id
and to_stable_id
.
As long as your class conforms to that interface, you are free to implement any custom logic that you like.
Database stabilization is the most straightforward of all the stabilizers, as it simply stores the filter's state in the database.
You need to create a migration to add the table that will hold filter ids and their corresponding state. If you choose to use this stabilizer, make sure you've published and run the provided migration:
rails generate migration CreateRefineStoredFilters state:json name:string filter_type:string
rails db:migrate
This will create a refine_stored_filters
table in your database.
The StoredFilter
class can be overridden with additional functionality (i.e. additional validations). The following is the base class:
module Refine
class StoredFilter < ApplicationRecord
validates_presence_of :state
self.table_name = "refine_stored_filters"
def refine_filter
Refine::Rails.configuration.stabilizer_classes[:db].new.from_stable_id(id: id)
end
def blueprint
JSON.parse(state)["blueprint"]
end
end
end
Render the filter with included stored filters view by setting stored_filter
to true
<%= render partial: 'filter_builder_dropdown', locals: { stored_filters: true } %>
The UrlEncoded stabilizer is a little different than the Database Stabilizer in that it doesn't save anything anywhere. What this stabilizer does is takes the filter's state and turns it into a rawurlencode
encoded string which then gets sent back to the frontend under the stable_id
key.
The common use case for the UrlEncoded stabilizer is to receive the stable_id
on the frontend, and then update the user's window location to include that id, eg example.com/users?filter={stable_id}
. Putting that stable id into the URL allows the user to copy, share, refresh, or otherwise store the URL and navigate back to exactly where they were before.
If your app sees high usage, then the UrlEncoded stabilizer is a great way to allow users to not lose all of their progress without having to save every filter to the database.
There is one extremely important consideration when using the UrlEncoded stabilizer: the size of the encoded string is directly proportional to the size of the filter's state. If your end user adds many conditions with very long values, then it is possible that your encoded state could end up being greater than 2,000 characters.
This consideration is so important because it is generally recommended that URLs not exceed 2,000 characters. If you're not putting the entire UrlEncoded string in the URL, then this consideration is irrelevant. If you are, however, you will need to consider the possibility of a user creating a filter that encodes to an extremely long string.
By default the string is gzipped before it's encoded, but you still need to consider the potential length of string.