
Security News
Another Round of TEA Protocol Spam Floods npm, But It’s Not a Worm
Recent coverage mislabels the latest TEA protocol spam as a worm. Here’s what’s actually happening.
jsonapi-query_builder
Advanced tools
Jsonapi::QueryBuilder serves the purpose of adding the json api query related SQL conditions to the already scoped
collection, usually used in controller index actions.
With the query builder we can easily define logic for query filters, attributes by which we can sort, and delegate
pagination parameters to the underlying paginator. Included relationships are automatically included via the
ActiveRecord::QueryMethods#includes, to prevent N+1 query problems.
Add this line to your application's Gemfile:
gem 'jsonapi-query_builder'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install jsonapi-query_builder
class UserQuery < Jsonapi::QueryBuilder::BaseQuery
## pagination
paginator Jsonapi::QueryBuilder::Paginator::Pagy # default paginator
## sorting
default_sort created_at: :desc
sorts_by :last_name
sorts_by :first_name, ->(collection, direction) { collection.order(name: direction) }
sorts_by :email, EmailSort
## filtering
filters_by :first_name
filters_by :last_name
filters_by :email, ->(collection, query) { collection.where('email ilike ?', "%#{query}%") }
filters_by :type, TypeFilter
filters_by :mrn, MrnInMemoryFilter
end
class UsersController < ApplicationController
def index
user_query = UserQuery.new(User, params.to_unsafe_hash)
render json: user_query.results, status: :ok
end
end
The query class is initialized using a collection and query parameters. Since query parameters are referenced explicitly
we can pass them as an unsafe hash. Jsonapi::QueryBuilder::BaseQuery should not be responsible for scoping records
based on current user permissions, or for any other type of scoping. It's only responsibility is to support
the json:api querying. Use pundit or similar for policy scoping, custom query objects for other scoping, and then
pass the scoped collection to the Jsonapi::QueryBuilder::BaseQuery object.
Pagination support is configurable using the paginator method to define the paginator. It defaults to the Pagy
paginator, a lightweight and fast paginator. Other paginators currently supported are Kaminari and an implementation
of keyset pagination. Before using these paginators we need to explicitly require the gems in our Gemfile and the
paginator file in question. Additionally one can implement it's own paginator by inheriting
from Jsonapi::QueryBuilder::Paginator::BasePaginator. The minimum required implementation is a #paginate method that
receives page params and returns a page of the collection. It can return the pagination details as the second item of
the returned array, that can be used in the serializer for pagination metadata.
require "jsonapi/query_builder/paginator/kaminari"
paginator Jsonapi::QueryBuilder::Paginator::Kaminari
require "jsonapi/query_builder/paginator/keyset"
paginator Jsonapi::QueryBuilder::Paginator::Keyset
Sorting has a fallback to an unique attribute which defaults to the id attribute. This ensures deterministic paginated
collection responses. You can override the unique_sort_attribute in the query object.
# set the unique sort attribute
unique_sort_attribute :email
# use compound unique sort attributes
unique_sort_attributes :created_at, :email
The default_sort can be set to sort by any field like created_at timestamp or similar. It is only used if no sort
parameter is set, unlike the unique_sort_attribute which is always appended as the last sort attribute. The parameters
are passed directly to the underlying active record relation, so the usual ordering options are possible. It is also
possible to define the default sort with a lambda or by passing a sort object.
default_sort :created_at
# or
default_sort created_at: :desc
# or
default_sort ->(collection) { collection.order(created_at: :desc) }
# or
default_sort SortObject
sorts_by denotes which attributes can be used for sorting. Sorting parameters are usually parsed from the
json:api sort query parameter in the order they are given. So sort=-first_name,email would translate to
{ first_name: :desc, email: :asc }
sorts_by :first_name
sorts_by :email
sorts_by also supports passing a lambda to implement a custom order or reorder function. The parameters passed to the
lamdba are collection and the direction of the order, which is either :desc or :asc.
sorts_by :first_name, ->(collection, direction) { collection.order(name: direction) }
But since we're devout followers of the SOLID principles, we can define a sort class that responds to #results method,
which returns the sorted collection. Under the hood the sort class is initialized with the current scope and the
direction parameter.
filters_by :first_name
# => collection.where(first_name: params.dig(:filter, :first_name)) if params.dig(:filter, :first_name).present?
filters_by :email, ->(collection, query) { collection.where('email ilike ?', "%#{query}%") }
# => collection.where('email ilike ?', "%#{params.dig(:filter, :email)}%") if params.dig(:filter, :email).present?
We can define a filter class that responds to #results method, which returns the filtered collection results. Under
the hood the filter class is initialized with the current scope and the query parameter. However, if the object responds
to a call method it sends the current scope and the query parameter to that instead. This is great if you're using
query objects for ActiveRecord scopes, you can easily use them to filter with as well.
filters_by :type, TypeFilter
The filter class could look something like
class TypeFilter < Jsonapi::QueryBuilder::BaseFilter
def results
collection.where(type: query.split(','))
end
end
Sometimes you need to perform in-memory filtering, for example when database attributes are encrypted. In that case, those filters should be applied last, the order of definition in the query object matters.
class MrnFilter < Jsonapi::QueryBuilder::BaseFilter
def results
collection.select { |record| /#{query}/.match?(record.mrn) }
end
end
You can override the filter query parameter name by passing the query_parameter option.
filters_by :first_name, query_parameter: 'name'
# => collection.where(first_name: params.dig(:filter, :name)) if params.dig(:filter, :name).present?
allow_nil option changes the filter conditional to allow explicit checks for an attribute null value.
filters_by :first_name, allow_nil: true
# => collection.where(first_name: params.dig(:filter, :first_name)) if params[:filter]&.key?(:first_name)
The conditional when the filter is applied can also be defined explicitly. Note that these options override the
allow_nil option, as the condition if defined explicitly and you should handle nil explicitly as well.
filters_by :first_name, if: ->(query) { query.length >= 2 }
# => collection.where(first_name: params.dig(:filter, :first_name)) if params.dig(:filter, :first_name) >= 2
filters_by :first_name, unless: ->(query) { query.length < 2 }
# => collection.where(first_name: params.dig(:filter, :first_name)) unless params.dig(:filter, :first_name) < 2
When you're using a filter class you can pass a symbol to the :if and :unless options which invokes the method on
the filter class.
filters_by :type, TypeFilter, if: :correct_type?
# => type_filter = TypeFilter.new(collection, query); type_filter.results if type_filter.correct_type?
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can
also run bin/console for an interactive prompt that will allow you to experiment.
We're using standardrb and lefthook. You can install lefthook hooks via lefthook install. It will run linters and
standardrb checks before commits, and a bundle audit + whole spec suite before push.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the
version number in version.rb, and then run LEFTHOOK=0 bundle exec rake release, which will create a git tag for the
version, push git commits and tags, and push the .gem file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/infinum/jsonapi-query_builder.
The gem is available as open source under the terms of the MIT License.
FAQs
Unknown package
We found that jsonapi-query_builder demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Recent coverage mislabels the latest TEA protocol spam as a worm. Here’s what’s actually happening.

Security News
PyPI adds Trusted Publishing support for GitLab Self-Managed as adoption reaches 25% of uploads

Research
/Security News
A malicious Chrome extension posing as an Ethereum wallet steals seed phrases by encoding them into Sui transactions, enabling full wallet takeover.