class Rack::Reducer

Declaratively filter data via URL params, in any Rack app, with any ORM.

Constants

EMPTY_PARAMS
VERSION

Public Class Methods

call(params, dataset:, filters:) click to toggle source

Call Rack::Reducer as a function instead of creating a named reducer

# File lib/rack/reducer.rb, line 16
def call(params, dataset:, filters:)
  new(dataset, *filters).apply(params)
end
new(dataset, *filters) click to toggle source

Instantiate a Reducer that can filter `dataset` via `#apply`. @param [Object] dataset an ActiveRecord::Relation, Sequel::Dataset,

or other class with chainable methods

@param [Array<Proc>] filters An array of lambdas whose keyword arguments

name the URL params you will use as filters

@example Create a reducer and use it in a Sinatra app

DB = Sequel.connect(ENV['DATABASE_URL'])

MyReducer = Rack::Reducer.new(
  DB[:artists],
  lambda { |name:| where(name: name) },
  lambda { |genre:| where(genre: genre) },
)

get '/artists' do
  @artists = MyReducer.apply(params)
  @artists.to_json
end
# File lib/rack/reducer.rb, line 39
def initialize(dataset, *filters)
  @dataset = dataset
  @filters = filters
  @default_filters = filters.select do |filter|
    filter.required_argument_names.empty?
  end
end

Public Instance Methods

apply(url_params) click to toggle source

Run `@filters` against `url_params` @param [Hash, ActionController::Parameters, nil] url_params

a Rack-compatible params hash

@return `@dataset` with the matching filters applied

# File lib/rack/reducer.rb, line 51
def apply(url_params)
  if url_params.empty?
    # Return early with the unfiltered dataset if no default filters exist
    return fresh_dataset if @default_filters.empty?

    # Run only the default filters
    filters, params = @default_filters, EMPTY_PARAMS
  else
    # This request really does want filtering; run a full reduction
    filters, params = @filters, url_params.to_unsafe_h.deep_symbolize_keys
  end

  reduce(params, filters)
end

Private Instance Methods

fresh_dataset() click to toggle source

Rails Model.all relations get query-cached by default, which has caused filterless requests to load stale data. This method busts the query cache. See github.com/chrisfrank/rack-reducer/issues/11

# File lib/rack/reducer.rb, line 82
def fresh_dataset
  @dataset.clone
end
reduce(params, filters) click to toggle source
# File lib/rack/reducer.rb, line 68
def reduce(params, filters)
  filters.reduce(fresh_dataset) do |data, filter|
    next data unless filter.satisfies?(params)

    data.instance_exec(
      **params.slice(*filter.all_argument_names),
      &filter
    )
  end
end