-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sift supports specifying types of scope arguments #48
base: master
Are you sure you want to change the base?
Conversation
@@ -372,7 +372,7 @@ Style/StringLiterals: | |||
Style/TrailingCommaInArguments: | |||
Description: 'Checks for trailing comma in argument lists.' | |||
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' | |||
EnforcedStyleForMultiline: comma | |||
EnforcedStyleForMultiline: no_comma |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was driving me nuts, but I'm definitely willing to revert this.
lib/procore-sift.rb
Outdated
@@ -66,7 +66,16 @@ def sort_fields | |||
|
|||
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) | |||
if type.respond_to?(:key?) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm definitely open to feedback on how to structure supplying the scope types.
lib/sift/filter.rb
Outdated
else | ||
handler.call(collection, parameterize(value), params, scope_params) | ||
handler(value, params).call(collection) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A fairly large change here: handlers are initialized with data from the request, but they are always called with just the collection. This allows us to treat the default handler, where handler, and scope handler as having the same interface once they are constructed without passing any unused values.
lib/sift/filter.rb
Outdated
@@ -38,7 +39,7 @@ def validation_field | |||
end | |||
|
|||
def type_validator | |||
@type_validator ||= Sift::TypeValidator.new(param, type) | |||
@type_validator ||= Sift::TypeValidator.new(param, validation_type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allows us to validate the "chief" parameter to scope filters.
@@ -51,10 +52,6 @@ def param | |||
|
|||
private | |||
|
|||
def parameterize(value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The handlers take over responsibility for parsing the raw values from the request. This allows the scope handler to use its types to choose the right parsing strategy.
@@ -73,12 +70,44 @@ def valid_scope_params?(scope_params) | |||
scope_params.is_a?(Array) && scope_params.all? { |symbol| symbol.is_a?(Symbol) } | |||
end | |||
|
|||
def handler | |||
parameter.handler |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't have Parameter be responsible for the handler, because scope filters need more than just the "chief" parameter to handle.
@@ -8,35 +8,5 @@ def initialize(param, type, internal_name = param) | |||
@type = type | |||
@internal_name = internal_name | |||
end | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parameter loses responsibility for parsing values because we want to parse things other than just the "chief" parameter.
@supports_boolean = options.fetch(:supports_boolean, false) | ||
@supports_ranges = options.fetch(:supports_ranges, false) | ||
@supports_json = options.fetch(:supports_json, false) | ||
def initialize(value:, type: nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ValueParser can be responsible for determining supports_boolean, supports_ranges, and supports_json from the type, so it doesn't need to be passed in. This helps us parse values for things other than the "chief" parameter.
@@ -1,3 +1,3 @@ | |||
module Sift | |||
VERSION = "0.13.0".freeze | |||
VERSION = "0.14.0".freeze |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm bumping the minor here because if anything is using the Sift internals (Sift::ValueParser, Sift::Parameter) it could be broken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the term "chief parameter" could use some clarification, but I think I know what you are saying its the root type.
lib/procore-sift.rb
Outdated
raise ArgumentError, "filter_on: Only type: :scope can have subtypes. Expecting the form `type: {scope: [type, {param: type}]}`" | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe move this validation into the Filter class since it has access to type
and scope_types
it could do this validation there pretty easily such as in the validate_scope_types!
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. I'll make this change unless we decide on some other filter_on
interface.
if type != :scope || scope_types.empty? | ||
type | ||
else | ||
scope_types.first |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only a single scope type is allowed right? If thats the case should the argument be changed to scope_type
with a default value of nil?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another thought is that scope_type param is unrelated to other filters. Should we create a different filter class to have better argument cohesion for scopes and so that way we aren't complicating other filters with logic specific to scopes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scope_types can have something like [:int, {foo: :datetime}] or [:int]. I can definitely see making some kind of inheritance / mixin relationship between new classes WhereFilter and ScopeFilter. I think they both support the default:
option so there's some overlap. I totally agree that the frequently unused scope_params
and scope_types
arguments are a smell.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of this @amayer171 ?
#49
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
merged...
Make subclasses of Sift::Filter for Where and Scope
end | ||
end | ||
``` | ||
Passing `?filters[with_priority]=[1,2]` will call the `with_priority` scope with the array, `[1, 2]`, instead of `"[1,2]"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There doesn't seem to be much consensus over how arrays should be used in query parameters.
https://medium.com/raml-api/arrays-in-query-params-33189628fa68
But =1,2
appears to be a more common pattern than =[1, 2]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sift uses the JSON standard for array parameters. My(unpopular?) opinion is that we should use either the Rails standard filters[foo][]=1&filters[foo][]=2
or POST JSON and use the X-http-method-override
header to specify GET simulation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do think this PR is not affected by that decision, however. We could change this code to parse however we want:
https://github.com/procore/sift/blob/master/lib/sift/value_parser.rb#L24-L33
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a fan of filters[foo][]=1&filters[foo][]=2
is that really a Rails standard? Can you explain X-http-method-override
with GET simulation in more detail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@njbbaer
By setting a header with key X-Http-Method-Override
and value get
in a JSON POST request (for example) you can specify that the request should be handled as a GET in servers that support this header (Rails servers).
So you can POST with a body of {"filters": {"foo": [1,2]}}
with rails treating this as a GET with params of { filters: {foo: [1,2]}}
. This sidesteps all issues with URL length, and allows your request to use actual JSON instead of mixing URL parameters with JSON. In practice it works really well. Here is example code from the vector_processing repo:
def large_get(path, params, timeout: 180)
url = vapid_uri_base + path
retries = 0
begin
raw_response = RestClient::Request.execute(
url: url,
method: :post,
headers: {'Accept' => 'application/json;charset=utf-8', 'Content-Type' => 'application/json', 'Authorization' => authorization.authorization_header, 'X-Http-Method-Override' => 'get'},
open_timeout: 3,
timeout: timeout,
verify_ssl: VectorProcessing::Vapid::Authorization.verify_ssl?,
payload: params.to_json
)
JSON.parse(raw_response.body.force_encoding('UTF-8'))
rescue RestClient::Unauthorized
retry if authorization.refresh_success? && (retries+=1) < 5
rescue RestClient::ExceptionWithResponse => rcewr
raise ErrorResponseException.new("Error Response #{rcewr.response&.code || 'missing'} to X-Http-Method-Override 'get' on POST '#{url}'")
end
end
This requires no change to Vapid; it's supported by Rails out-of-the-box.
Co-authored-by: Nate Baer <[email protected]>
Please check out the README.md changes for the idea. I'm extremely open to feedback.
I was motivated to do this as a feature request from @bradleyrzeller
There's some significant refactoring in this PR. I went through several attempts before I arrived at these changes. Hopefully they seem ok. I'll add comments about my rationale.