procore-sift 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f18552ca0480672282156cc7664312b98dbad772
4
- data.tar.gz: 81612b0c72fa8827496fe23af9e804993d4d1484
2
+ SHA256:
3
+ metadata.gz: 3faa95a328bd7cb7f4252ed830e08bb0032a367526b223f3b24857b955912c2d
4
+ data.tar.gz: ef52247a7f9fd757d18fa662f7b7d7e4f9ab49e45b80de9ae68e18690fba80d8
5
5
  SHA512:
6
- metadata.gz: 0dc882e598295f080ead00b2b791ecbd5a86722ac7108ce37d00835b7c47790d4dc90d70140cc0bfd8d0b9f66b8b0861072b24e7c84f5ac69170365aedf82572
7
- data.tar.gz: 753c9f44ea32b532bc1fb99d5d1b840255c3132eaf5bcfab5842eec2778439b2069e0c631d4a17aba4b41dec583b17f243e341c9f7557ea4aae1ae30ab785403
6
+ metadata.gz: aa7c02c0b73fcefc709b7030b636611ec6a821f14df32080fb21c4e4ddf0a50288ad298775bd8919196b47e30652e4d02a1303d1189d9e7703aadc435d7be54e
7
+ data.tar.gz: 4d1dfefa31e8b1537d9605d8724fa2fceb16739ed62300da2e38ec8a8ba4f567a47712b8b7fd82f52dc229495a14524896d7eab9dbbcfcde665fc9113dfe3ea6
data/README.md CHANGED
@@ -46,6 +46,7 @@ Every filter must have a type, so that Sift knows what to do with it. The curren
46
46
  * time - Filter on a time column
47
47
  * datetime - Filter on a datetime column
48
48
  * scope - Filter on an ActiveRecord scope
49
+ * jsonb - Filter on a jsonb column (supported only in PostgreSQL)
49
50
 
50
51
  ### Filter on Scopes
51
52
  Just as your filter values are used to scope queries on a column, values you
@@ -122,6 +123,36 @@ The following types support ranges
122
123
  * time
123
124
  * datetime
124
125
 
126
+ ### Filter on jsonb column
127
+
128
+ 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
129
+
130
+ **Array**
131
+
132
+ It should be sent an array in the URL Query parameters
133
+ * `?filters[metadata]=[1,2]`
134
+
135
+ **key-value**
136
+
137
+ It can be passed one or more Key values:
138
+ * `?filters[metadata]={"data_1":"test"}`
139
+ * `?filters[metadata]={"data_1":"test","data_2":"[1,2]"}`
140
+
141
+ When the value is an array, it will filter records with those values or more, for example:
142
+
143
+ * `?filters[metadata]={"data_2":"[1,2]"}`
144
+
145
+ Will return records with next values stored in the JSONB column `metadata`:
146
+ ```ruby
147
+ { data_2: 1 }
148
+ { data_2: 2 }
149
+ { data_2: [1] }
150
+ { data_2: [2] }
151
+ { data_2: [1,2] }
152
+ { data_2: [1,2,3] }
153
+ ```
154
+
155
+
125
156
  ### Filter on JSON Array
126
157
  `int` type filters support sending the values as an array in the URL Query parameters. For example `?filters[id]=[1,2]`. This is a way to keep payloads smaller for GET requests. When URI encoded this will become `filters%5Bid%5D=%5B1,2%5D` which is much smaller the standard format of `filters%5Bid%5D%5B%5D=1&&filters%5Bid%5D%5B%5D=2`.
127
158
 
@@ -10,6 +10,7 @@ require "sift/scope_handler"
10
10
  require "sift/where_handler"
11
11
  require "sift/validators/valid_int_validator"
12
12
  require "sift/validators/valid_date_range_validator"
13
+ require "sift/validators/valid_json_validator"
13
14
 
14
15
  module Sift
15
16
  extend ActiveSupport::Concern
@@ -13,7 +13,8 @@ module Sift
13
13
  {
14
14
  supports_boolean: supports_boolean?,
15
15
  supports_ranges: supports_ranges?,
16
- supports_json: supports_json?
16
+ supports_json: supports_json?,
17
+ supports_json_object: supports_json_object?
17
18
  }
18
19
  end
19
20
 
@@ -32,7 +33,11 @@ module Sift
32
33
  end
33
34
 
34
35
  def supports_json?
35
- type == :int
36
+ [:int, :jsonb].include?(type)
37
+ end
38
+
39
+ def supports_json_object?
40
+ type == :jsonb
36
41
  end
37
42
 
38
43
  def supports_boolean?
@@ -4,6 +4,7 @@ module Sift
4
4
  DATETIME_RANGE_PATTERN = { format: { with: /\A.+(?:[^.]\.\.\.[^.]).+\z/, message: "must be a range" }, valid_date_range: true }.freeze
5
5
  DECIMAL_PATTERN = { numericality: true, allow_nil: true }.freeze
6
6
  BOOLEAN_PATTERN = { inclusion: { in: [true, false] }, allow_nil: true }.freeze
7
+ JSON_PATTERN = { valid_json: true }.freeze
7
8
 
8
9
  WHITELIST_TYPES = [:int,
9
10
  :decimal,
@@ -13,7 +14,8 @@ module Sift
13
14
  :date,
14
15
  :time,
15
16
  :datetime,
16
- :scope].freeze
17
+ :scope,
18
+ :jsonb].freeze
17
19
 
18
20
  def initialize(param, type)
19
21
  @param = param
@@ -32,6 +34,8 @@ module Sift
32
34
  DECIMAL_PATTERN
33
35
  when :boolean
34
36
  BOOLEAN_PATTERN
37
+ when :jsonb
38
+ JSON_PATTERN
35
39
  end
36
40
  end
37
41
 
@@ -0,0 +1,14 @@
1
+ class ValidJsonValidator < ActiveModel::EachValidator
2
+ def validate_each(record, attribute, value)
3
+ record.errors.add attribute, "must be a valid JSON" unless valid_json?(value)
4
+ end
5
+
6
+ private
7
+
8
+ def valid_json?(value)
9
+ value = value.strip if value.is_a?(String)
10
+ JSON.parse(value)
11
+ rescue JSON::ParserError
12
+ false
13
+ end
14
+ end
@@ -5,6 +5,7 @@ module Sift
5
5
  @supports_boolean = options.fetch(:supports_boolean, false)
6
6
  @supports_ranges = options.fetch(:supports_ranges, false)
7
7
  @supports_json = options.fetch(:supports_json, false)
8
+ @supports_json_object = options.fetch(:supports_json_object, false)
8
9
  @value = normalized_value(value, type)
9
10
  end
10
11
 
@@ -15,26 +16,39 @@ module Sift
15
16
  elsif parse_as_boolean?
16
17
  boolean_value
17
18
  elsif parse_as_json?
18
- array_from_json
19
+ supports_json_object ? parse_json_and_values : array_from_json
19
20
  else
20
21
  value
21
22
  end
22
23
  end
23
24
 
25
+ def parse_json(string)
26
+ JSON.parse(string)
27
+ rescue JSON::ParserError
28
+ string
29
+ end
30
+
31
+ def parse_json_and_values
32
+ parsed_jsonb = parse_json(value)
33
+ return parsed_jsonb if parsed_jsonb.is_a?(Array) || parsed_jsonb.is_a?(String)
34
+
35
+ parsed_jsonb.each_with_object({}) do |key_value, hash|
36
+ hash[key_value.first] = parse_json(key_value.last.to_s)
37
+ end
38
+ end
39
+
24
40
  def array_from_json
25
- result = JSON.parse(value)
41
+ result = parse_json(value)
26
42
  if result.is_a?(Array)
27
43
  result
28
44
  else
29
45
  value
30
46
  end
31
- rescue JSON::ParserError
32
- value
33
47
  end
34
48
 
35
49
  private
36
50
 
37
- attr_reader :value, :type, :supports_boolean, :supports_json, :supports_ranges
51
+ attr_reader :value, :type, :supports_boolean, :supports_json, :supports_json_object, :supports_ranges
38
52
 
39
53
  def parse_as_range?(raw_value=value)
40
54
  supports_ranges && raw_value.to_s.include?("...")
@@ -1,3 +1,3 @@
1
1
  module Sift
2
- VERSION = "0.13.0".freeze
2
+ VERSION = "0.14.0".freeze
3
3
  end
@@ -5,7 +5,28 @@ module Sift
5
5
  end
6
6
 
7
7
  def call(collection, value, _params, _scope_params)
8
- collection.where(@param.internal_name => value)
8
+ if @param.type == :jsonb
9
+ apply_jsonb_conditions(collection, value)
10
+ else
11
+ collection.where(@param.internal_name => value)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def apply_jsonb_conditions(collection, value)
18
+ return collection.where("#{@param.internal_name} @> ?", val.to_s) if value.is_a?(Array)
19
+
20
+ value.each do |key, val|
21
+ condition = if val.is_a?(Array)
22
+ "('{' || TRANSLATE(#{@param.internal_name}->>'#{key}', '[]','') || '}')::int[] && ARRAY[?]"
23
+ else # Single Value
24
+ val = val.to_s
25
+ "#{@param.internal_name}->>'#{key}' = ?"
26
+ end
27
+ collection = collection.where(condition, val)
28
+ end
29
+ collection
9
30
  end
10
31
  end
11
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: procore-sift
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Procore Technologies
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-27 00:00:00.000000000 Z
11
+ date: 2020-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -115,6 +115,7 @@ files:
115
115
  - lib/sift/type_validator.rb
116
116
  - lib/sift/validators/valid_date_range_validator.rb
117
117
  - lib/sift/validators/valid_int_validator.rb
118
+ - lib/sift/validators/valid_json_validator.rb
118
119
  - lib/sift/value_parser.rb
119
120
  - lib/sift/version.rb
120
121
  - lib/sift/where_handler.rb
@@ -138,8 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
139
  - !ruby/object:Gem::Version
139
140
  version: '0'
140
141
  requirements: []
141
- rubyforge_project:
142
- rubygems_version: 2.6.14
142
+ rubygems_version: 3.1.3
143
143
  signing_key:
144
144
  specification_version: 4
145
145
  summary: Summary of Sift.