MongoidHashQuery
Introduction
Important: text with ~~strikethrough~~ are future features not yet implemented
That's the little brother of ActiveHashRelation gem.
Simple gem that allows you to manipulate Mongoid queries using Hash/JSON. For instance:
apply_filters(resource, {name: 'RPK', start_date: {leq: "2014-10-19"}, act_status: "ongoing"})
It's perfect for filtering a collection of resources on APIs.
It should be noted that apply_filters
calls MongoidHashQuery::FilterApplier
class
underneath with the same params.
*A user could retrieve resources based on unknown attributes (attributes not returned from the API) by brute forcing which might or might not be a security issue. If you don't like that check whitelisting.
New! You can now do aggregation queries.
Installation
Add this line to your application's Gemfile:
gem 'mongoid_hash_query'
And then execute:
$ bundle
Or install it yourself as:
$ gem install mongoid_hash_query
How to use
The gem exposes only one method: apply_filters(resource, hash_params, include_associations: false, model: nil)
. resource
is expected to be an Mongoid::Criteria.
That way, you can add your custom filters before calling apply_filters
.
In order to use it you have to include MongoidHashQuery module in your class. For instance in a Rails API controller you would do:
class Api::V1::ResourceController < Api::V1::BaseController
include MongoidHashQuery
def index
resources = apply_filters(Resource.all, params)
= policy_scope(resource)
render json: resources
end
end
The API
Fields
For each param, apply_filters
method will search in the model (derived from the first param, or explicitly defined as the last param) all the record's field names ~~and associations~~. ~~(filtering based on scopes are not working at the moment but will be supported soon).~~ For each column, if there is such a param, it will apply the filter based on the column type. The following column types are supported:
Id
Mongo's documents have an id
, names _id
of type BSON::ObjectId. You can just pass in id
instead of _id
:
{id: 5}
- ~~
{id: [1,3,4,5,6,7]}
~~
Integer, Float, BigDecimal, Date, Time or Datetime
You can apply an equality filter:
{example_column: 500}
or using a hash as a value you get more options:{example_field: {le: 500}}
{example_field: {leq: 500}}
{example_field: {ge: 500}}
{example_field: {geq: 500}}
Of course you can provide a compination of those like:
{example_column: {geq: 500, le: 1000}}
The same api is for Float, BigDecimal, Date, Time or Datetime.
Boolean
I am not sure how Mongoid converts input to boolean but according to that spec a value to be true must be one of the following: [true, 1, '1', 't', 'T', 'true', 'TRUE']
. Anything else is false.
{example_field: true}
{example_field: 0}
String
You can apply an equality filter:
{example_field: test}
Soon you will be able to send in regex..
Hash
You can apply an equality filter on hashes like that:
{example_field: {foo: 'bar', bar: 'foo'}}
Or you can apply a regex in example_field.foo like that:
{example_field: {foo: {regex: true, value: 'bar', ignore_case: true}, bar: 'foo'}}
Limit
A limit param defines the number of returned resources. For instance:
{limit: 10}
However I would strongly advice you to use a pagination gem like Kaminari, and use page
and per_page
params.
Sorting
You can apply sorting using the property
and order
attributes. For instance:
{created_at: 'desc'}
If there is no column named after the property value, sorting is skipped.
Associations (later)
~~If the association is a belongs_to
or has_one
, then the hash key name must be in singular. If the association is has_many
the attribute must be in plural reflecting the association type. When you have, in your hash, filters for an association, the sub-hash is passed in the association's model. For instance, let's say a user has many microposts and the following filter is applied (could be through an HTTP GET request on controller's index method):~~
{email: [email protected], microposts: {created_at { leq: 12-9-2014} }
Scopes
If you want to filter based on a scope in a model, the scope names should go under scopes
sub-hash. For instance the following:
{ scopes: { planned: true } }
will run the .planned
scope on the resource.
Whitelisting
If you don't want to allow a column/association/scope just remove it from the params hash.
Filter Classes (later)
~~Sometimes, especially on larger projects, you have specific classes that handle
the input params outside the controllers. You can configure the gem to look for
those classes and call apply_filters
which will apply the necessary filters when
iterating over associations.~~
~~In an initializer:~~
#config/initializers/mongoid_hash_query.rb
MongoidHashQuery.configure do |config|
config.has_filter_classes = true
config.filter_class_prefix = 'Api::V1::'
config.filter_class_suffix = 'Filter'
end
~~With the above settings, when the association name is resource
,
Api::V1::ResourceFilter.new(resource, params[resource]).apply_filters
will be
called to apply the filters in resource association.~~
Aggregation Queries
Sometimes we need to ask the database queries that act on the collection but don't want back an array of elements but a value instead! Now you can do that by simply calling the aggregations method inside the controller:
aggregations(resource, {
aggregate: {
integer_column: { avg: true, max: true, min: true, sum: true },
float_column: {avg: true, max: true, min: true },
datetime_column: { max: true, min: true }
}
})
and you will get a hash (HashWithIndifferentAccess) back that holds all your aggregations like:
{
aggregations: {
"float_column"=>{"avg"=>25.5, "max"=>50, "min"=>1},
"integer_column"=>{"avg"=>4.38, "sum"=>219, "max"=>9, "min"=>0},
"datetime_at"=>{"max"=>2015-06-11 20:59:14 UTC, "min"=>2015-06-11 20:59:12 UTC}
}
}
These attributes usually go to the "meta" section of your serializer. In that way it's easy to parse them in the front-end (for ember check here). Please note that you should apply the aggregations after you apply the filters (if there any) but before you apply pagination!
Contributing
- Fork it ( https://github.com/kollegorna/mongoid_hash_query/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request