lib/rack/reducer.rb in rack-reducer-1.1.2 vs lib/rack/reducer.rb in rack-reducer-2.0.0
- old
+ new
@@ -1,87 +1,82 @@
# frozen_string_literal: true
-require_relative 'reducer/reduction'
+require_relative 'reducer/refinements'
require_relative 'reducer/middleware'
-require_relative 'reducer/warnings'
module Rack
- # Declaratively filter data via URL params, in any Rack app.
- module Reducer
- # Create a Reduction object that can filter +dataset+ via +#apply+.
+ # Declaratively filter data via URL params, in any Rack app, with any ORM.
+ class Reducer
+ using Refinements
+
+ class << self
+ # make ::create an alias of ::new, for compatibility with v1
+ alias create new
+
+ # Call Rack::Reducer as a function instead of creating a named reducer
+ def call(params, dataset:, filters:)
+ new(dataset, *filters).apply(params)
+ end
+ end
+
+ # 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
- # @return Rack::Reducer::Reduction
# @example Create a reducer and use it in a Sinatra app
# DB = Sequel.connect(ENV['DATABASE_URL'])
- # MyReducer = Rack::Reducer.create(
+ #
+ # 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
- def self.create(dataset, *filters)
- Reduction.new(dataset, *filters)
+ def initialize(dataset, *filters)
+ @dataset = dataset
+ @filters = filters
+ @default_filters = filters.select do |filter|
+ filter.required_argument_names.empty?
+ end
end
- # Filter a dataset without creating a Reducer first.
- # Note that this approach is a bit slower and less memory-efficient than
- # creating a Reducer via ::create. Use ::create when you can.
- #
- # @param params [Hash] Rack-compatible URL params
- # @param dataset [Object] A dataset, e.g. one of your App's models
- # @param filters [Array<Proc>] An array of lambdas with keyword arguments
- # @example Call Rack::Reducer as a function in a Sinatra app
- # get '/artists' do
- # @artists = Rack::Reducer.call(params, dataset: Artist.all, filters: [
- # lambda { |name:| where(name: name) },
- # lambda { |genre:| where(genre: genre) },
- # ])
- # end
- def self.call(params, dataset:, filters:)
- Reduction.new(dataset, *filters).apply(params)
- end
+ # Run `@filters` against `url_params`
+ # @param [Hash, ActionController::Parameters, nil] url_params
+ # a Rack-compatible params hash
+ # @return `@dataset` with the matching filters applied
+ def apply(url_params)
+ if url_params.empty?
+ # Return early with the unfiltered dataset if no default filters exist
+ return @dataset if @default_filters.empty?
- # Mount Rack::Reducer as middleware
- # @deprecated
- # Rack::Reducer.new will become an alias of ::create in v2.0.
- # To mount middleware that will still work in 2.0, write
- # "use Rack::Reducer::Middleware" instead of "use Rack::Reducer"
- def self.new(app, options = {})
- warn "#{caller(1..1).first}}\n#{Warnings[:new]}"
- Middleware.new(app, options)
+ # 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.symbolize_keys
+ end
+
+ reduce(params, filters)
end
- # Extend Rack::Reducer to get +reduce+ and +reduces+ as class-methods
- #
- # @example Make an "Artists" model reducible
- # class Artist < SomeORM::Model
- # extend Rack::Reducer
- # reduces self.all, filters: [
- # lambda { |name:| where(name: name) },
- # lambda { |genre:| where(genre: genre) },
- # ]
- # end
- # Artist.reduce(params)
- #
- # @deprecated
- # Rack::Reducer's mixin-style is deprecated and may be removed in 2.0.
- # To keep using Rack::Reducer in your models, create a Reducer constant.
- # class MyModel < ActiveRecord::Base
- # MyReducer = Rack::Reducer.create(dataset, *filter_functions)
- # end
- # MyModel::MyReducer.call(params)
- def reduces(dataset, filters:)
- warn "#{caller(1..1).first}}\n#{Warnings[:reduces]}"
- reducer = Reduction.new(dataset, *filters)
- define_singleton_method :reduce do |params|
- reducer.apply(params)
+ private
+
+ def reduce(params, filters)
+ filters.reduce(@dataset) do |data, filter|
+ next data unless filter.satisfies?(params)
+
+ data.instance_exec(
+ **params.slice(*filter.all_argument_names),
+ &filter
+ )
end
end
+
+ EMPTY_PARAMS = {}.freeze
+ private_constant :EMPTY_PARAMS
end
end