Class: JsonApiServer::Filter
- Inherits:
-
Object
- Object
- JsonApiServer::Filter
- Defined in:
- lib/json_api_server/filter.rb
Overview
Implements filter parameters per JSON API Spec: http://jsonapi.org/recommendations/#filtering. The spec says: "The filter query parameter is reserved for filtering data. Servers and clients SHOULD use this key for filtering operations."
ie., GET /topics?filter[id]=1,2&filter[book]=*potter
This class (1) whitelists filter params and (2) generates a sub-query based on filters. If a user requests an unsupported filter, a JsonApiServer::BadRequest exception is raised which renders a 400 error.
Currently supports only ActiveRecord::Relation. Filters are combined with AND.
Usage:
A filter request will look like: /topics?filter=1,2&filter=*potter
Configurations:
Configurations look like: [ { id: { type: 'Integer' } }, { tags: { builder: :pg_jsonb_ilike_array } }, { published: { type: 'Date' } }, { published1: { col_name: :published, type: 'Date' } }, :location, { book: { wildcard: :both } }, { search: { builder: :model_query, method: :search } } ]
whitelist
Filter attributes are whitelisted. Specify filters you want to support in filter configs.
:type
:type (data type) defaults to String. Filter values are cast to this type. Supported types are:
- String
- Integer
- Date (Note: invalid Date casts to nil)
- DateTime (Note: invalid DateTime casts to nil)
- Float (untested)
- BigDecimal (untested)
:col_name
If a filter name is different from its model column/attribute, specify the column/attribute with :col_name.
:wildcard
A filter can enable wildcarding with the :wildcard option. :both wildcards both sides, :left wildcards the left, :right wildcards the right. A user triggers wildcarding by preceding a filter value with a * character (i.e., *weather).
/comments?filter=*weather => "comments"."comment" LIKE '%weather%'
Additional wildcard/like filters are available for Postgres.
ILIKE for case insensitive searches:
- pg_ilike: JsonApiServer::PgIlike
For searching a JSONB array - case sensitive:
- pg_jsonb_array: JsonApiServer::PgJsonbArray
For searching a JSONB array - case insensitive:
- pg_jsonb_ilike_array: JsonApiServer::PgJsonbIlikeArray
builder: :model_query
A filter can be configured to call a model's singleton method.
Example:
[ { search: { builder: :model_query, method: :search } } ]
Request:
/comments?filter[search]=tweet
The singleton method search will be called on the model specified in the filter constructor.
builder:
Specify a specific filter builder to handle the query. The list of default builders is in JsonApiServer::Configuration.
[ { tags: { builder: :pg_jsonb_ilike_array } } ]
As mentioned above, there are additional filter builders for Postgres. Custom filter builders can be added. In this example, it's using the :pg_jsonb_ilike_array builder which performs a case insensitve search on a JSONB array column.
Features
IN statement
Comma separated filter values translate into an IN statement. /topics?filter=1,2 => "topics"."id" IN (1,2)'
Operators
The following operators are supported:
=, <, >, >=, <=, !=
Example:
/comments?filter=>=20
note: special characters should be encoded -> /comments?filter=%3E%3D20
Searching a Range
Searching a range can be achieved with two filters for the same model attribute and operators:
Configuration: [ { published: { type: 'Date' } }, { published1: { col_name: :published, type: 'Date' } } ]
Request:
/topics?filter=>1998-01-01&filter=<1999-12-31
Produces a query like:
("topics"."published" > '1998-01-01') AND ("topics"."published" < '1999-12-31')
Custom Filters
Custom filters can be added. Filters should inherit from JsonApiServer::FilterBuilder.
Example:
In config/initializers/json_api_server.rb
Create custom fitler.
module JsonApiServer class MyCustomFilter < FilterBuilder def to_query(model) model.where("#full_column_name(model) LIKE :val", val: "%#value%") end end end
Update :filter_builders attribute to include your builder.
JsonApiServer.configure do |c| c.base_url = 'http://localhost:3001' c.filter_builders = c.filter_builders.merge(my_custom_builder: JsonApiServer::MyCustomFilter) c.logger = Rails.logger end
and then use it in your controllers...
c.filter_options = [
{ names: { builder: :my_custom_builder } }
]
Note:
- JsonApiServer::Builder class provides an easier way to use this class.
Instance Attribute Summary collapse
-
#model ⇒ Object
readonly
ActiveRecord::Base model passed in constructor.
-
#params ⇒ Object
readonly
Query parameters from #request.
-
#permitted ⇒ Object
readonly
Filter configs passed in constructor.
-
#request ⇒ Object
readonly
ActionDispatch::Request passed in constructor.
Instance Method Summary collapse
-
#filter_params ⇒ Object
Filter params from query parameters.
-
#initialize(request, model, permitted = []) ⇒ Filter
constructor
Arguments: - request - ActionDispatch::Request - model - ActiveRecord::Base model.
-
#meta_info ⇒ Object
Hash with filter meta information.
-
#relation ⇒ Object
(also: #query)
Returns an ActiveRecord Relation object (query fragment) which can be merged with another.
Constructor Details
#initialize(request, model, permitted = []) ⇒ Filter
Arguments:
- request - ActionDispatch::Request
- model - ActiveRecord::Base model. Used to generate sub-query.
- permitted (Array) - Defaults to empty array. Filter configurations.
185 186 187 188 189 190 |
# File 'lib/json_api_server/filter.rb', line 185 def initialize(request, model, permitted = []) @request = request @model = model @permitted = permitted.is_a?(Array) ? permitted : [] @params = request.query_parameters end |
Instance Attribute Details
#model ⇒ Object (readonly)
ActiveRecord::Base model passed in constructor.
176 177 178 |
# File 'lib/json_api_server/filter.rb', line 176 def model @model end |
#params ⇒ Object (readonly)
Query parameters from #request.
173 174 175 |
# File 'lib/json_api_server/filter.rb', line 173 def params @params end |
#permitted ⇒ Object (readonly)
Filter configs passed in constructor.
179 180 181 |
# File 'lib/json_api_server/filter.rb', line 179 def permitted @permitted end |
#request ⇒ Object (readonly)
ActionDispatch::Request passed in constructor.
170 171 172 |
# File 'lib/json_api_server/filter.rb', line 170 def request @request end |
Instance Method Details
#filter_params ⇒ Object
Filter params from query parameters.
193 194 195 |
# File 'lib/json_api_server/filter.rb', line 193 def filter_params @filter ||= params[:filter] || {} end |
#meta_info ⇒ Object
Hash with filter meta information. It echos untrusted user input (no sanitizing).
i.e., { filter: [ 'id: 1,2', 'comment: *weather' ] }
222 223 224 225 226 227 228 229 |
# File 'lib/json_api_server/filter.rb', line 222 def @meta_info ||= begin { filter: filter_params.each_with_object([]) do |(attr, val), result| result << "#{attr}: #{val}" if attr.present? && val.present? end } end end |
#relation ⇒ Object Also known as: query
Returns an ActiveRecord Relation object (query fragment) which can be merged with another.
199 200 201 202 203 204 205 206 207 208 |
# File 'lib/json_api_server/filter.rb', line 199 def relation @conditions ||= begin filter_params.each_with_object(model.all) do |(attr, val), result| if attr.present? && val.present? query = query_for(attr, val) result.merge!(query) unless query.nil? # query.present? triggers a db call. end end end end |