Skip to content

Commit b13bccc

Browse files
authored
Merge pull request #54 from procore/allows-mutating-filters
Allows mutating filters
2 parents 9e55063 + 5e3f7a7 commit b13bccc

File tree

10 files changed

+130
-15
lines changed

10 files changed

+130
-15
lines changed

CHANGELOG.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,34 @@
11
## Unreleased
22

3+
## 0.16.0
4+
5+
- Adds a `tap` method to `filter_on` to support mutating filter values
6+
37
## 0.15.0
48

5-
* Support for `null` filtering by `jsonb` type
9+
- Support for `null` filtering by `jsonb` type
610

711
## 0.14.0
812

9-
* Add support for `jsonb` type (only for PostgreSQL)
13+
- Add support for `jsonb` type (only for PostgreSQL)
1014

1115
## 0.13.0
1216

1317
## 0.12.0
1418

15-
* Change gem name to procore-sift
19+
- Change gem name to procore-sift
1620

1721
## 0.11.0
1822

19-
* Rename gem to Sift
20-
* Add normalization and validation for date range values
21-
* Tightened up ValueParser by privatizing unnecessarily public attr_accessors
23+
- Rename gem to Sift
24+
- Add normalization and validation for date range values
25+
- Tightened up ValueParser by privatizing unnecessarily public attr_accessors
2226

2327
## 0.10.0
2428

25-
* Support for integer filtering of JSON arrays
29+
- Support for integer filtering of JSON arrays
2630

2731
## 0.9.2 (January 26, 2018)
2832

29-
* Rename gem to Brita
30-
* Publish to RubyGems
33+
- Rename gem to Brita
34+
- Publish to RubyGems

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ Scopes that accept no arguments are currently not supported.
7979

8080
#### Accessing Params with Filter Scopes
8181

82-
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.
82+
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.
8383

8484
```ruby
8585
class Post < ActiveRecord::Base
@@ -130,6 +130,25 @@ The following types support ranges
130130
- time
131131
- datetime
132132

133+
### Mutating Filters
134+
135+
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.
136+
137+
```ruby
138+
139+
class PostsController < ApplicationController
140+
include Sift
141+
142+
filter_on :expiration, type: :datetime, tap: ->(value, params) {
143+
value.split("...").
144+
map do |str|
145+
str.to_date.in_time_zone(LOCAL_TIME_ZONE)
146+
end.
147+
join("...")
148+
}
149+
end
150+
```
151+
133152
### Filter on jsonb column
134153

135154
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

lib/procore-sift.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ def sort_fields
6666
end
6767

6868
class_methods do
69-
def filter_on(parameter, type:, internal_name: parameter, default: nil, validate: nil, scope_params: [])
70-
filters << Filter.new(parameter, type, internal_name, default, validate, scope_params)
69+
def filter_on(parameter, type:, internal_name: parameter, default: nil, validate: nil, scope_params: [], tap: nil)
70+
filters << Filter.new(parameter, type, internal_name, default, validate, scope_params, tap)
7171
end
7272

7373
def filters

lib/sift/filter.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ module Sift
44
class Filter
55
attr_reader :parameter, :default, :custom_validate, :scope_params
66

7-
def initialize(param, type, internal_name, default, custom_validate = nil, scope_params = [])
7+
def initialize(param, type, internal_name, default, custom_validate = nil, scope_params = [], tap = ->(value, _params) { value })
88
@parameter = Parameter.new(param, type, internal_name)
99
@default = default
1010
@custom_validate = custom_validate
1111
@scope_params = scope_params
12+
@tap = tap
1213
raise ArgumentError, "scope_params must be an array of symbols" unless valid_scope_params?(scope_params)
1314
raise "unknown filter type: #{type}" unless type_validator.valid_type?
1415
end
@@ -24,7 +25,9 @@ def apply!(collection, value:, active_sorts_hash:, params: {})
2425
elsif should_apply_default?(value)
2526
default.call(collection)
2627
else
27-
handler.call(collection, parameterize(value), params, scope_params)
28+
parameterized_values = parameterize(value)
29+
processed_values = @tap.present? ? @tap.call(parameterized_values, params) : parameterized_values
30+
handler.call(collection, processed_values, params, scope_params)
2831
end
2932
end
3033
# rubocop:enable Lint/UnusedMethodArgument

lib/sift/sort.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class Sort
1717
def initialize(param, type, internal_name = param, scope_params = [])
1818
raise "unknown filter type: #{type}" unless WHITELIST_TYPES.include?(type)
1919
raise "scope params must be an array" unless scope_params.is_a?(Array)
20+
2021
@parameter = Parameter.new(param, type, internal_name)
2122
@scope_params = scope_params
2223
end

lib/sift/subset_comparator.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ def initialize(array)
55
end
66

77
def include?(other)
8+
other = [other] unless other.is_a?(Array)
9+
810
@array.to_set >= other.to_set
911
end
1012
end

lib/sift/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Sift
2-
VERSION = "0.15.0".freeze
2+
VERSION = "0.16.0".freeze
33
end

test/dummy/app/controllers/posts_controller.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
class PostsController < ApplicationController
22
include Sift
33

4+
LOCAL_TIME_ZONE = "America/New_York"
5+
46
filter_on :id, type: :int
57
filter_on :priority, type: :int
68
filter_on :rating, type: :decimal
@@ -17,6 +19,14 @@ class PostsController < ApplicationController
1719
filter_on :french_bread, type: :string, internal_name: :title
1820
filter_on :body2, type: :scope, internal_name: :body2, default: ->(c) { c.order(:body) }
1921

22+
filter_on :expiration, type: :datetime, tap: ->(value, params) {
23+
value.split("...").
24+
map do |str|
25+
str.to_date.in_time_zone(LOCAL_TIME_ZONE)
26+
end.
27+
join("...")
28+
}
29+
2030
# rubocop:disable Style/RescueModifier
2131
filter_on :id_array, type: :int, internal_name: :id, validate: ->(validator) {
2232
value = validator.instance_variable_get("@id_array")

test/filter_test.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,12 @@ class FilterTest < ActiveSupport::TestCase
7575

7676
assert_equal expected_validation, filter.validation(nil)
7777
end
78+
79+
test "it accepts a tap parameter" do
80+
filter = Sift::Filter.new("hi", :boolean, "hi", nil, nil, [], ->(_value, _params) {
81+
false
82+
})
83+
84+
assert_equal false, filter.instance_variable_get("@tap").call(true, {})
85+
end
7886
end

test/filtrator_test.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,72 @@ class FiltratorTest < ActiveSupport::TestCase
197197

198198
assert_equal Post.expired_before_ordered_by_body("2017-12-31", :asc).to_a, collection.to_a
199199
end
200+
201+
test "it can utilize the tap parameter to mutate a param" do
202+
Post.create!(priority: 5, expiration: "2017-01-01T00:00:00+00:00")
203+
Post.create!(priority: 5, expiration: "2017-01-02T00:00:00+00:00")
204+
Post.create!(priority: 7, expiration: "2020-10-20T00:00:00+00:00")
205+
206+
filter = Sift::Filter.new(
207+
:expiration,
208+
:datetime,
209+
:expiration,
210+
nil,
211+
nil,
212+
[],
213+
->(value, params) {
214+
if params[:mutate].present?
215+
"2017-01-02T00:00:00+00:00...2017-01-02T00:00:00+00:00"
216+
else
217+
value
218+
end
219+
},
220+
)
221+
collection = Sift::Filtrator.filter(
222+
Post.all,
223+
{
224+
filters: { expiration: "2017-01-01...2017-01-01" },
225+
mutate: true
226+
},
227+
[filter],
228+
)
229+
230+
assert_equal 3, Post.count
231+
assert_equal 1, collection.count
232+
233+
assert_equal Post.where(expiration: "2017-01-02").to_a, collection.to_a
234+
end
235+
236+
test "it can filter on scopes that need multiple values from params with a tap" do
237+
Post.create!(priority: 5, expiration: "2017-01-01")
238+
Post.create!(priority: 5, expiration: "2017-01-02")
239+
Post.create!(priority: 7, expiration: "2020-10-20")
240+
241+
filter = Sift::Filter.new(
242+
:ordered_expired_before_and_priority,
243+
:scope,
244+
:ordered_expired_before_and_priority,
245+
nil,
246+
nil,
247+
[:date, :priority],
248+
->(_value, _params) {
249+
"ASC"
250+
},
251+
)
252+
collection = Sift::Filtrator.filter(
253+
Post.all,
254+
{
255+
filters: { ordered_expired_before_and_priority: "DESC" },
256+
priority: 5,
257+
date: "2017-12-31"
258+
},
259+
[filter],
260+
)
261+
262+
assert_equal 3, Post.count
263+
assert_equal 2, Post.ordered_expired_before_and_priority("ASC", date: "2017-12-31", priority: 5).count
264+
assert_equal 2, collection.count
265+
266+
assert_equal Post.ordered_expired_before_and_priority("ASC", date: "2017-12-31", priority: 5).to_a, collection.to_a
267+
end
200268
end

0 commit comments

Comments
 (0)