Skip to content

Commit

Permalink
Merge pull request #54 from procore/allows-mutating-filters
Browse files Browse the repository at this point in the history
Allows mutating filters
  • Loading branch information
grahamleslie authored Mar 16, 2021
2 parents 9e55063 + 5e3f7a7 commit b13bccc
Show file tree
Hide file tree
Showing 10 changed files with 130 additions and 15 deletions.
22 changes: 13 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
## Unreleased

## 0.16.0

- Adds a `tap` method to `filter_on` to support mutating filter values

## 0.15.0

* Support for `null` filtering by `jsonb` type
- Support for `null` filtering by `jsonb` type

## 0.14.0

* Add support for `jsonb` type (only for PostgreSQL)
- Add support for `jsonb` type (only for PostgreSQL)

## 0.13.0

## 0.12.0

* Change gem name to procore-sift
- Change gem name to procore-sift

## 0.11.0

* Rename gem to Sift
* Add normalization and validation for date range values
* Tightened up ValueParser by privatizing unnecessarily public attr_accessors
- Rename gem to Sift
- Add normalization and validation for date range values
- Tightened up ValueParser by privatizing unnecessarily public attr_accessors

## 0.10.0

* Support for integer filtering of JSON arrays
- Support for integer filtering of JSON arrays

## 0.9.2 (January 26, 2018)

* Rename gem to Brita
* Publish to RubyGems
- Rename gem to Brita
- Publish to RubyGems
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Scopes that accept no arguments are currently not supported.

#### Accessing Params with Filter Scopes

Filters with `type: :scope` have access to the params hash by passing in the desired keys to the `scope_params`. The keys passed in will be returned as a hash with their associated values, and should always appear as the last argument in your scope.
Filters with `type: :scope` have access to the params hash by passing in the desired keys to the `scope_params`. The keys passed in will be returned as a hash with their associated values.

```ruby
class Post < ActiveRecord::Base
Expand Down Expand Up @@ -130,6 +130,25 @@ The following types support ranges
- time
- datetime

### Mutating Filters

Filters can be mutated before the filter is applied using the `tap` argument. This is useful, for example, if you need to adjust the time zone of a `datetime` range filter.

```ruby

class PostsController < ApplicationController
include Sift

filter_on :expiration, type: :datetime, tap: ->(value, params) {
value.split("...").
map do |str|
str.to_date.in_time_zone(LOCAL_TIME_ZONE)
end.
join("...")
}
end
```

### Filter on jsonb column

Usually JSONB columns stores values as an Array or an Object (key-value), in both cases the parameter needs to be sent in a JSON format
Expand Down
4 changes: 2 additions & 2 deletions lib/procore-sift.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ def sort_fields
end

class_methods do
def filter_on(parameter, type:, internal_name: parameter, default: nil, validate: nil, scope_params: [])
filters << Filter.new(parameter, type, internal_name, default, validate, scope_params)
def filter_on(parameter, type:, internal_name: parameter, default: nil, validate: nil, scope_params: [], tap: nil)
filters << Filter.new(parameter, type, internal_name, default, validate, scope_params, tap)
end

def filters
Expand Down
7 changes: 5 additions & 2 deletions lib/sift/filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ module Sift
class Filter
attr_reader :parameter, :default, :custom_validate, :scope_params

def initialize(param, type, internal_name, default, custom_validate = nil, scope_params = [])
def initialize(param, type, internal_name, default, custom_validate = nil, scope_params = [], tap = ->(value, _params) { value })
@parameter = Parameter.new(param, type, internal_name)
@default = default
@custom_validate = custom_validate
@scope_params = scope_params
@tap = tap
raise ArgumentError, "scope_params must be an array of symbols" unless valid_scope_params?(scope_params)
raise "unknown filter type: #{type}" unless type_validator.valid_type?
end
Expand All @@ -24,7 +25,9 @@ def apply!(collection, value:, active_sorts_hash:, params: {})
elsif should_apply_default?(value)
default.call(collection)
else
handler.call(collection, parameterize(value), params, scope_params)
parameterized_values = parameterize(value)
processed_values = @tap.present? ? @tap.call(parameterized_values, params) : parameterized_values
handler.call(collection, processed_values, params, scope_params)
end
end
# rubocop:enable Lint/UnusedMethodArgument
Expand Down
1 change: 1 addition & 0 deletions lib/sift/sort.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Sort
def initialize(param, type, internal_name = param, scope_params = [])
raise "unknown filter type: #{type}" unless WHITELIST_TYPES.include?(type)
raise "scope params must be an array" unless scope_params.is_a?(Array)

@parameter = Parameter.new(param, type, internal_name)
@scope_params = scope_params
end
Expand Down
2 changes: 2 additions & 0 deletions lib/sift/subset_comparator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ def initialize(array)
end

def include?(other)
other = [other] unless other.is_a?(Array)

@array.to_set >= other.to_set
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/sift/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Sift
VERSION = "0.15.0".freeze
VERSION = "0.16.0".freeze
end
10 changes: 10 additions & 0 deletions test/dummy/app/controllers/posts_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
class PostsController < ApplicationController
include Sift

LOCAL_TIME_ZONE = "America/New_York"

filter_on :id, type: :int
filter_on :priority, type: :int
filter_on :rating, type: :decimal
Expand All @@ -17,6 +19,14 @@ class PostsController < ApplicationController
filter_on :french_bread, type: :string, internal_name: :title
filter_on :body2, type: :scope, internal_name: :body2, default: ->(c) { c.order(:body) }

filter_on :expiration, type: :datetime, tap: ->(value, params) {
value.split("...").
map do |str|
str.to_date.in_time_zone(LOCAL_TIME_ZONE)
end.
join("...")
}

# rubocop:disable Style/RescueModifier
filter_on :id_array, type: :int, internal_name: :id, validate: ->(validator) {
value = validator.instance_variable_get("@id_array")
Expand Down
8 changes: 8 additions & 0 deletions test/filter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,12 @@ class FilterTest < ActiveSupport::TestCase

assert_equal expected_validation, filter.validation(nil)
end

test "it accepts a tap parameter" do
filter = Sift::Filter.new("hi", :boolean, "hi", nil, nil, [], ->(_value, _params) {
false
})

assert_equal false, filter.instance_variable_get("@tap").call(true, {})
end
end
68 changes: 68 additions & 0 deletions test/filtrator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,72 @@ class FiltratorTest < ActiveSupport::TestCase

assert_equal Post.expired_before_ordered_by_body("2017-12-31", :asc).to_a, collection.to_a
end

test "it can utilize the tap parameter to mutate a param" do
Post.create!(priority: 5, expiration: "2017-01-01T00:00:00+00:00")
Post.create!(priority: 5, expiration: "2017-01-02T00:00:00+00:00")
Post.create!(priority: 7, expiration: "2020-10-20T00:00:00+00:00")

filter = Sift::Filter.new(
:expiration,
:datetime,
:expiration,
nil,
nil,
[],
->(value, params) {
if params[:mutate].present?
"2017-01-02T00:00:00+00:00...2017-01-02T00:00:00+00:00"
else
value
end
},
)
collection = Sift::Filtrator.filter(
Post.all,
{
filters: { expiration: "2017-01-01...2017-01-01" },
mutate: true
},
[filter],
)

assert_equal 3, Post.count
assert_equal 1, collection.count

assert_equal Post.where(expiration: "2017-01-02").to_a, collection.to_a
end

test "it can filter on scopes that need multiple values from params with a tap" do
Post.create!(priority: 5, expiration: "2017-01-01")
Post.create!(priority: 5, expiration: "2017-01-02")
Post.create!(priority: 7, expiration: "2020-10-20")

filter = Sift::Filter.new(
:ordered_expired_before_and_priority,
:scope,
:ordered_expired_before_and_priority,
nil,
nil,
[:date, :priority],
->(_value, _params) {
"ASC"
},
)
collection = Sift::Filtrator.filter(
Post.all,
{
filters: { ordered_expired_before_and_priority: "DESC" },
priority: 5,
date: "2017-12-31"
},
[filter],
)

assert_equal 3, Post.count
assert_equal 2, Post.ordered_expired_before_and_priority("ASC", date: "2017-12-31", priority: 5).count
assert_equal 2, collection.count

assert_equal Post.ordered_expired_before_and_priority("ASC", date: "2017-12-31", priority: 5).to_a, collection.to_a
end
end

0 comments on commit b13bccc

Please sign in to comment.