<repo_name>croatech/filtering<file_sep>/lib/filtering.rb
require 'filtering/version'
require 'filtering/base'

module Filtering
  # Your code goes here...
end
<file_sep>/README.md
# Filtering

Gem for comfortable filtering of ActiveRecord queries.

## Installation

Add this line to your application's Gemfile:

```ruby
gem 'filtering'
```

And then execute:

    $ bundle

Or install it yourself as:

    $ gem install filtering

## Usage
All you need to do is inherit custom filter service from `Filtering::Base` and define params for filtering.

### Example filter service

```
class Filters::UsersFilter < Filtering::Base
  def initialize(params, args)
    super(params, args)
  end

  private

  # Required methods

  def relation
    User.all
  end

  def plain_acessible_params
    %i[city age]
  end

  def complex_acessible_params
    %i[name]
  end

  # Complex filters

  def filter_by_name(name)
    result.where('name ILIKE ? or name_auto ILIKE ?', "%#{name}%", "%#{name}%")
  end
end

```
There are private methods:

`relation` is an ActiveRecord initial relation which must be filtered

`plain_acessible_params` array of params for an auto-filtering by `where`, so if you have `%i[city age]` will be called `User.where(city: city).where(age: age)`

`complex_acessible_params` array of params for filters with custom logic. If you have some params in that method you have to create methods for those custom filters with format: `filter_by_{param}`

### Calling
Controller usage example:

```
def index
  render json: Filters::UsersFilter.new(params, page: params[:page], order: 'id DESC').call
end
```
If you don't use Kaminari just delete page from initializer, the same with order param

## License

The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).

## Code of Conduct

Everyone interacting in the Filtering project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/filtering/blob/master/CODE_OF_CONDUCT.md).
<file_sep>/lib/filtering/base.rb
class Filtering::Base
  def initialize(params, args = {})
    @params = params
    @plain_params = permit(params, plain_acessible_params)
    @complex_params = permit(params, complex_acessible_params)
    @page = args[:page]
    @order = args[:order]
  end

  def call
    @result = relation

    filter_by_plain_params unless plain_params.empty?
    filter_by_complex_params unless complex_params.empty?

    paginate if page.present? && result.respond_to?(:page)
    ordering if order.present?
    grouping if group.present?

    return result
  end

  protected

  attr_reader :params, :plain_params, :complex_params, :page, :order, :group, :result

  def relation
    raise 'relation method not implemented. Add this method to calling class and add AR relation, for example Model.all'
  end

  def permit(params, acessible_params)
    return {} if acessible_params.nil?

    if params.respond_to?(:permit)
      params.permit(acessible_params)
    else
      params.symbolize_keys.slice(*acessible_params.map(&:to_sym))
    end
  end

  def plain_acessible_params; end

  def complex_acessible_params; end

  private

  def filter_by_plain_params
    return if plain_params.empty?

    plain_params.each do |key, value|
      @result = result.where("#{key}": value) if value.present?
    end
  end

  def filter_by_complex_params
    return if complex_params.empty?

    raise_if_complex_method_not_implemented

    complex_params.each do |key, value|
      @result = send("filter_by_#{key}", value)
    end
  end

  def raise_if_complex_method_not_implemented
    complex_acessible_params.each do |param|
      method_name = "filter_by_#{param}"
      raise "#{method_name} method not implemented. Add #{method_name} private method to #{self.class.name} or remove #{param} param from complex_acessible_params" unless self.private_methods.include?(method_name.to_sym)
    end
  end

  def paginate
    @result = result.page(page)
  end

  def ordering
    @result = result.order(order)
  end

  def grouping
    @result = result.group(group)
  end
end