jsonapi-query_builder 0.1.6.pre → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: edcba945298b8eadf8c9eb952ee3fed13ba3c8c822afa15dea5126102b689584
4
- data.tar.gz: acb2b63e334270e21874c4985467c29953faa2abda3af6065aac26b0963767af
3
+ metadata.gz: e613355258e5be273292baacd4b8c8aecba03b29005212749868412857feb678
4
+ data.tar.gz: 46f0f548604944cb9572b90c44a9e3ca5f15e1f8a47985cb588135f8cade2794
5
5
  SHA512:
6
- metadata.gz: d80d0e5ac1d07327c1f8737eb5b52439022914238a650ee926175c9f2e3b6f1bf0abecb15812e3e8f8241f8d607a5c2a8b9094169a1ba2e869363a4ecc588a0e
7
- data.tar.gz: a17154de4293836988b82af81fef9e2a925f9a153d5ba1ae161178e75ad7b6571a0c5f38b2f0f3b96661f001ec3442191b70cdc85754489ebbd80cee78a4bb21
6
+ metadata.gz: 58546d6561f997295a4505780acb1ad9ff210bd8f1c1390638283d9af62cb1f4d4e89b69e646a902851a1840590b49ce3480ef42f1fff6861a0b7d92f3f27e80
7
+ data.tar.gz: 851a87dab11ee9194318f4fc5ea437d842ae561b2d5e10448adf796050cc0abb866a73c16ddde0e4b5f3d9757b583498344137d11ecdeaa793dc101a285af5f7
data/README.md CHANGED
@@ -29,7 +29,9 @@ Or install it yourself as:
29
29
  class UserQuery < Jsonapi::QueryBuilder::BaseQuery
30
30
  ## sorting
31
31
  default_sort created_at: :desc
32
- sorts_by :first_name, :last_name, :email
32
+ sorts_by :last_name
33
+ sorts_by :first_name, ->(collection, direction) { collection.order(name: direction) }
34
+ sorts_by :email, EmailSort
33
35
 
34
36
  ## filtering
35
37
  filters_by :first_name
@@ -71,14 +73,26 @@ are passed directly to the underlying active record relation, so the usual order
71
73
  ```ruby
72
74
  default_sort created_at: :desc
73
75
  ```
74
- #### Enabling sorting for attributes
76
+ #### Enabling simple sorting for attributes
75
77
  `sorts_by` denotes which attributes can be used for sorting. Sorting parameters are usually parsed from the
76
- `json:api` sort query parameter in order they are given. So `sort=-first_name,email` would translate to
77
- `{ first_name: :desc, email: :asc }`
78
+ `json:api` sort query parameter in the order they are given. So `sort=-first_name,email` would translate to
79
+ `{ first_name: :desc, email: :asc }`
78
80
  ```ruby
79
- sorts_by :first_name, :email
81
+ sorts_by :first_name
82
+ sorts_by :email
83
+ ```
84
+ #### Sorting with lambdas
85
+ `sorts_by` also supports passing a lambda to implement a custom order or reorder function. The parameters passed to the
86
+ lamdba are collection and the direction of the order, which is either `:desc` or `:asc`.
87
+ ```ruby
88
+ sorts_by :first_name, ->(collection, direction) { collection.order(name: direction) }
80
89
  ```
81
90
 
91
+ #### Sorting with sort classes
92
+ But since we're devout followers of the SOLID principles, we can define a sort class that responds to
93
+ `#results` method, which returns the sorted collection. Under the hood the sort class is initialized with
94
+ the current scope and the direction parameter.
95
+
82
96
  ### Filtering
83
97
 
84
98
  #### Simple exact match filters
@@ -94,10 +108,11 @@ filters_by :email, ->(collection, query) { collection.where('email ilike ?', "%#
94
108
  ```
95
109
 
96
110
  #### Filter classes
97
- But since we're devout followers of the SOLID principles, we can define a filter class that responds to
98
- `#results` method, which returns the filtered collection results. Under the hood the filter class is initialized with
99
- the current scope and the query parameter is passed to the call method. This is great if you're using query objects for
100
- ActiveRecord scopes, you can easily use them to filter as well.
111
+ We can define a filter class that responds to `#results` method, which returns the filtered collection results. Under
112
+ the hood the filter class is initialized with the current scope and the query parameter. However, if the object responds
113
+ to a `call` method it sends the current scope and the query parameter to that instead. This is great if you're using
114
+ query objects for ActiveRecord scopes, you can easily use them to filter with as well.
115
+
101
116
  ```ruby
102
117
  filters_by :type, TypeFilter
103
118
  ```
@@ -151,8 +166,8 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
151
166
  also run `bin/console` for an interactive prompt that will allow you to experiment.
152
167
 
153
168
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
154
- version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
155
- push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
169
+ version number in `version.rb`, and then run `LEFTHOOK=0 bundle exec rake release`, which will create a git tag for the
170
+ version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
156
171
 
157
172
  ## Contributing
158
173
 
@@ -38,13 +38,14 @@ Gem::Specification.new do |spec|
38
38
 
39
39
  spec.required_ruby_version = "~> 2.5"
40
40
 
41
- spec.add_runtime_dependency "activerecord", ">= 5", "<= 6.1"
41
+ spec.add_runtime_dependency "activerecord", ">= 5"
42
42
  spec.add_runtime_dependency "pagy", "~> 3.5"
43
43
 
44
44
  spec.add_development_dependency "bundler", "~> 2.0"
45
45
  spec.add_development_dependency "rake", "~> 13.0"
46
46
  spec.add_development_dependency "rspec", "~> 3.0"
47
47
  spec.add_development_dependency "standardrb"
48
+ spec.add_development_dependency "standard"
48
49
  spec.add_development_dependency "rubocop-rspec"
49
50
  spec.add_development_dependency "lefthook"
50
51
  end
@@ -10,3 +10,4 @@ require "pagy/extras/items"
10
10
  require "jsonapi/query_builder/version"
11
11
  require "jsonapi/query_builder/base_query"
12
12
  require "jsonapi/query_builder/base_filter"
13
+ require "jsonapi/query_builder/base_sort"
@@ -5,11 +5,14 @@ module Jsonapi
5
5
  class BaseFilter
6
6
  attr_reader :collection, :query
7
7
 
8
+ # @param [ActiveRecord::Relation] collection
9
+ # @param [String] query the query value used for filtering
8
10
  def initialize(collection, query)
9
11
  @collection = collection
10
12
  @query = query
11
13
  end
12
14
 
15
+ # @return [ActiveRecord::Relation] Collection with the filter applied
13
16
  def results
14
17
  raise NotImplementedError, "#{self.class} should implement #results"
15
18
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "jsonapi/query_builder/mixins/filtering"
3
+ require "jsonapi/query_builder/mixins/filter"
4
4
  require "jsonapi/query_builder/mixins/include"
5
5
  require "jsonapi/query_builder/mixins/paginate"
6
6
  require "jsonapi/query_builder/mixins/sort"
@@ -8,34 +8,48 @@ require "jsonapi/query_builder/mixins/sort"
8
8
  module Jsonapi
9
9
  module QueryBuilder
10
10
  class BaseQuery
11
- include Mixins::Filtering
11
+ include Mixins::Filter
12
12
  include Mixins::Include
13
13
  include Mixins::Paginate
14
14
  include Mixins::Sort
15
15
 
16
- attr_reader :collection, :params
16
+ attr_accessor :collection, :params
17
17
 
18
+ # @param [ActiveRecord::Relation] collection
19
+ # @param [Hash] params Json:Api query parameters
18
20
  def initialize(collection, params)
19
21
  @collection = collection
20
22
  @params = params.deep_symbolize_keys
21
23
  end
22
24
 
25
+ # @return [ActiveRecord::Relation] A collection with eager loaded relationships based on include params, filtered,
26
+ # ordered and lastly, paginated.
27
+ # @note Pagination details are saved to an instance variable and can be accessed via the #pagination_details attribute reader
23
28
  def results
24
29
  collection
25
- .yield_self(&method(:sort))
26
30
  .yield_self(&method(:add_includes))
31
+ .yield_self(&method(:sort))
27
32
  .yield_self(&method(:filter))
28
33
  .yield_self(&method(:paginate))
29
34
  end
30
35
 
36
+ # @param [integer, string] id
37
+ # @return [Object]
38
+ # @raise [ActiveRecord::RecordNotFound] if the record by the id is not found
31
39
  def find(id)
32
40
  find_by! id: id
33
41
  end
34
42
 
43
+ # Finds the record by the id parameter the class is instantiated with
44
+ # @return (see #find)
45
+ # @raise (see #find)
35
46
  def record
36
47
  find_by! id: params[:id]
37
48
  end
38
49
 
50
+ # @param [Hash] kwargs Attributes with required values
51
+ # @return (see #find)
52
+ # @raise [ActiveRecord::RecordNotFound] if the record by the arguments is not found
39
53
  def find_by!(**kwargs)
40
54
  add_includes(collection).find_by!(kwargs)
41
55
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsonapi
4
+ module QueryBuilder
5
+ class BaseSort
6
+ attr_reader :collection, :direction
7
+
8
+ # @param [ActiveRecord::Relation] collection
9
+ # @param [Symbol] direction of the ordering, one of :asc or :desc
10
+ def initialize(collection, direction)
11
+ @collection = collection
12
+ @direction = direction
13
+ end
14
+
15
+ # @return [ActiveRecord::Relation] Collection with order applied
16
+ def results
17
+ raise NotImplementedError, "#{self.class} should implement #results"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsonapi
4
+ module QueryBuilder
5
+ module Mixins
6
+ module Filter
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ def supported_filters
11
+ @supported_filters || {}
12
+ end
13
+
14
+ # Registers an attribute to filter by. Filters are applied in the order they are registered, if they are
15
+ # applicable, so in-memory filters should be registered last.
16
+ #
17
+ # @param [Symbol] attribute The attribute which can be used to filter by
18
+ # @param [proc, Class] filter A proc or a filter class, defaults to a simple where(attribute => query)
19
+ # @param [Hash] options Additional filter options
20
+ # @option options [Symbol] :query_parameter used to override the filter query parameter name
21
+ # @option options [Boolean] :allow_nil changes the filter conditional to allow explicit checks for an
22
+ # attribute null value
23
+ # @option options [proc, Symbol] :if Define the conditional for applying the filter. If passed a symbol, a
24
+ # method with that name is invoked on the instantiated filter object
25
+ # @option options [proc, Symbol] :unless Define the conditional for applying the filter. If passed a symbol, a
26
+ # method with that name is invoked on the instantiated filter object
27
+ #
28
+ # @example Change the query parameter name
29
+ # filters_by :first_name, query_parameter: 'name'
30
+ # # => collection.where(first_name: params.dig(:filter, :name)) if params.dig(:filter, :name).present?
31
+ #
32
+ # @example Allow checks for null values
33
+ # filters_by :first_name, allow_nil: true
34
+ # # => collection.where(first_name: params.dig(:filter, :first_name)) if params[:filter]&.key?(:first_name)
35
+ #
36
+ # @example Change the filter condition
37
+ # filters_by :first_name, if: ->(query) { query.length >= 2 }
38
+ # # => collection.where(first_name: params.dig(:filter, :first_name)) if params.dig(:filter, :first_name) >= 2
39
+ # filters_by :first_name, unless: ->(query) { query.length < 2 }
40
+ # # => collection.where(first_name: params.dig(:filter, :first_name)) unless params.dig(:filter, :first_name) < 2
41
+ # filters_by :type, TypeFilter, if: :correct_type?
42
+ # # => TypeFilter.new(collection, query).yield_self { |filter| filter.results if filter.correct_type? }
43
+ def filters_by(attribute, filter = nil, **options)
44
+ filter ||= ->(collection, query) { collection.where(attribute => query) }
45
+ @supported_filters = {**supported_filters, attribute => [filter, options]}
46
+ end
47
+ end
48
+
49
+ # Filters the passed relation with the default filter params (parsed from the queries params) or with explicitly
50
+ # passed filter parameters.
51
+ # Iterates through registered filters so that the filter application order is settable from the backend side
52
+ # instead of being dependent on the query order from the clients. If the filter condition for the filter
53
+ # strategy is met, then the filter is applied to the collection. If the strategy responds to a call method it
54
+ # calls it with the collection and parameter's parsed sort direction, otherwise it instantiates the filter class
55
+ # with the collection and the parameter's query value and calls for the results.
56
+ # @param [ActiveRecord::Relation] collection
57
+ # @param [Object] filter_params Optional explicit filter params
58
+ # @return [ActiveRecord::Relation, Array] An AR relation is returned unless filters need to resort to in-memory
59
+ # filtering strategy, then an array is returned.
60
+ def filter(collection, filter_params = send(:filter_params))
61
+ self.class.supported_filters.reduce(collection) do |filtered_collection, supported_filter|
62
+ filter, options = serialize_filter(supported_filter, collection: filtered_collection, params: filter_params)
63
+
64
+ next filtered_collection unless options[:conditions].all? { |type, condition|
65
+ check_condition(condition, type, filter: filter, query: options[:query])
66
+ }
67
+
68
+ filter.respond_to?(:call) ? filter.call(filtered_collection, options[:query]) : filter.results
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def filter_params
75
+ params[:filter] || {}
76
+ end
77
+
78
+ def serialize_filter(supported_filter, collection:, params:)
79
+ attribute, (filter, options) = *supported_filter
80
+
81
+ options[:query_parameter] = options[:query_parameter]&.to_sym || attribute
82
+ options[:query] = params[options[:query_parameter]].presence
83
+ options[:conditions] = options.slice(:if, :unless).presence ||
84
+ {if: options[:query].present? || options[:allow_nil] && params.key?(options[:query_parameter])}
85
+
86
+ filter = filter.new(collection, options[:query]) unless filter.respond_to?(:call)
87
+
88
+ [filter, options]
89
+ end
90
+
91
+ def check_condition(condition, type, **opts)
92
+ (type == :if) == case condition
93
+ when Proc
94
+ condition.call(opts[:query])
95
+ when Symbol
96
+ opts[:filter].send(condition)
97
+ else
98
+ condition
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -4,6 +4,11 @@ module Jsonapi
4
4
  module QueryBuilder
5
5
  module Mixins
6
6
  module Include
7
+ # Eager loads the relationships that will be included in the response, based on the Json:Api
8
+ # include query parameter.
9
+ # @param [ActiveRecord::Relation] collection
10
+ # @param [Object] include_params Optional explicit include params
11
+ # @return [ActiveRecord::Relation] Collection with eager loaded included relations
7
12
  def add_includes(collection, include_params = send(:include_params))
8
13
  collection.includes(formatted_include_params(include_params))
9
14
  end
@@ -8,6 +8,11 @@ module Jsonapi
8
8
 
9
9
  attr_reader :pagination_details
10
10
 
11
+ # Paginates the collection and returns the requested page. Also sets the pagination details that can be used for
12
+ # displaying metadata in the Json:Api response.
13
+ # @param [ActiveRecord::Relation] collection
14
+ # @param [Object] page_params Optional explicit pagination params
15
+ # @return [ActiveRecord::Relation] Paged collection
11
16
  def paginate(collection, page_params = send(:page_params))
12
17
  @pagination_details, records = pagy collection, page: page_params[:number],
13
18
  items: page_params[:size],
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "jsonapi/query_builder/mixins/sort/param"
4
+
3
5
  module Jsonapi
4
6
  module QueryBuilder
5
7
  module Mixins
@@ -15,28 +17,57 @@ module Jsonapi
15
17
  @_unique_sort_attributes || [id: :asc]
16
18
  end
17
19
 
18
- def _sort_attributes
19
- @_sort_attributes || []
20
+ def supported_sorts
21
+ @supported_sorts || {}
20
22
  end
21
23
 
24
+ # Ensures deterministic ordering. Defaults to :id in ascending direction.
25
+ # @param [Array<Symbol, String, Hash>] attributes An array of attributes or a hash with the attribute and it's
26
+ # order direction.
22
27
  def unique_sort_attributes(*attributes)
23
28
  @_unique_sort_attributes = attributes
24
29
  end
30
+
25
31
  alias_method :unique_sort_attribute, :unique_sort_attributes
26
32
 
33
+ # The :default_sort: can be set to sort by any field like `created_at` timestamp or similar. It is only used
34
+ # if no sort parameter is set, unlike the `unique_sort_attribute` which is always appended as the last sort
35
+ # attribute. The parameters are passed directly to the underlying active record relation, so the usual
36
+ # ordering options are possible.
37
+ # @param [Symbol, Hash] options A default sort attribute or a Hash with the attribute and it's order direction.
27
38
  def default_sort(options)
28
39
  @_default_sort = options
29
40
  end
30
41
 
31
- def sorts_by(*attributes)
32
- @_sort_attributes = _sort_attributes + attributes
42
+ # Registers attribute that can be used for sorting. Sorting parameters are usually parsed from the `json:api`
43
+ # sort query parameter in the order they are given.
44
+ # @param [Symbol] attribute The "sortable" attribute
45
+ # @param [proc, Class] sort A proc or a sort class, defaults to a simple order(attribute => direction)
46
+ def sorts_by(attribute, sort = nil)
47
+ sort ||= ->(collection, direction) { collection.order(attribute => direction) }
48
+ @supported_sorts = {**supported_sorts, attribute => sort}
33
49
  end
34
50
  end
35
51
 
52
+ # Sorts the passed relation with the default sort params (parsed from the queries params) or with explicitly
53
+ # passed sort parameters.
54
+ # Parses each sort parameter and looks for the sorting strategy for it, if the strategy responds to a call
55
+ # method it calls it with the collection and parameter's parsed sort direction, otherwise it instantiates the
56
+ # sort class with the collection and the parameter's parsed sort direction and calls for the results. Finally it
57
+ # adds the unique sort attributes to enforce deterministic results. If sort params are blank, it adds the
58
+ # default sort attributes before setting the unique sort attributes.
59
+ # @param [ActiveRecord::Relation] collection
60
+ # @param [Object] sort_params Optional explicit sort params
61
+ # @return [ActiveRecord::Relation] Sorted relation
62
+ # @raise [Jsonapi::QueryBuilder::Mixins::Sort::UnpermittedSortParameters] if not all sort parameters are
63
+ # permitted
36
64
  def sort(collection, sort_params = send(:sort_params))
65
+ sort_params = Param.deserialize_params(sort_params)
66
+ ensure_permitted_sort_params!(sort_params) if sort_params
67
+
37
68
  collection
38
- .reorder(sort_params.nil? ? self.class._default_sort : formatted_sort_params(sort_params))
39
- .tap(&method(:add_unique_order_attributes))
69
+ .yield_self { |c| add_order_attributes(c, sort_params) }
70
+ .yield_self(&method(:add_unique_order_attributes))
40
71
  end
41
72
 
42
73
  private
@@ -45,21 +76,9 @@ module Jsonapi
45
76
  params[:sort]
46
77
  end
47
78
 
48
- def add_unique_order_attributes(collection)
49
- collection.order(*self.class._unique_sort_attributes)
50
- end
51
-
52
- def formatted_sort_params(sort_params)
53
- sort_params
54
- .split(",")
55
- .map(&:strip)
56
- .to_h { |attribute| attribute.start_with?("-") ? [attribute[1..-1], :desc] : [attribute, :asc] }
57
- .symbolize_keys
58
- .tap(&method(:ensure_permitted_sort_params!))
59
- end
60
-
61
79
  def ensure_permitted_sort_params!(sort_params)
62
- return if (unpermitted_parameters = sort_params.keys - self.class._sort_attributes.map(&:to_sym)).size.zero?
80
+ unpermitted_parameters = sort_params.map(&:attribute).map(&:to_sym) - self.class.supported_sorts.keys
81
+ return if unpermitted_parameters.size.zero?
63
82
 
64
83
  raise UnpermittedSortParameters, [
65
84
  unpermitted_parameters.to_sentence,
@@ -67,6 +86,25 @@ module Jsonapi
67
86
  "permitted sort attribute".pluralize(unpermitted_parameters.count)
68
87
  ].join(" ")
69
88
  end
89
+
90
+ def add_order_attributes(collection, sort_params)
91
+ return collection if self.class._default_sort.nil? && sort_params.blank?
92
+ return collection.order(self.class._default_sort) if sort_params.blank?
93
+
94
+ sort_params.reduce(collection) do |sorted_collection, sort_param|
95
+ sort = self.class.supported_sorts.fetch(sort_param.attribute.to_sym)
96
+
97
+ if sort.respond_to?(:call)
98
+ sort.call(sorted_collection, sort_param.direction)
99
+ else
100
+ sort.new(sorted_collection, sort_param.direction).results
101
+ end
102
+ end
103
+ end
104
+
105
+ def add_unique_order_attributes(collection)
106
+ collection.order(*self.class._unique_sort_attributes)
107
+ end
70
108
  end
71
109
  end
72
110
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsonapi
4
+ module QueryBuilder
5
+ module Mixins
6
+ module Sort
7
+ class Param
8
+ attr_reader :descending, :attribute
9
+
10
+ def initialize(param)
11
+ @descending, @attribute = deserialize(param)
12
+ end
13
+
14
+ class << self
15
+ def deserialize_params(sort_params)
16
+ (sort_params || "").split(",").map(&method(:new))
17
+ end
18
+ end
19
+
20
+ def deserialize(param)
21
+ _, descending, attribute = param.strip.match(/^(?<descending>-)?(?<attribute>.*)$/).to_a
22
+
23
+ [descending, attribute]
24
+ end
25
+
26
+ def serialize
27
+ [descending, attribute].compact.join
28
+ end
29
+
30
+ def direction
31
+ descending.present? ? :desc : :asc
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jsonapi
4
4
  module QueryBuilder
5
- VERSION = "0.1.6.pre"
5
+ VERSION = "0.1.7"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi-query_builder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6.pre
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jure Cindro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-01 00:00:00.000000000 Z
11
+ date: 2021-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -17,9 +17,6 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '5'
20
- - - "<="
21
- - !ruby/object:Gem::Version
22
- version: '6.1'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,9 +24,6 @@ dependencies:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: '5'
30
- - - "<="
31
- - !ruby/object:Gem::Version
32
- version: '6.1'
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: pagy
35
29
  requirement: !ruby/object:Gem::Requirement
@@ -100,6 +94,20 @@ dependencies:
100
94
  - - ">="
101
95
  - !ruby/object:Gem::Version
102
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: standard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
103
111
  - !ruby/object:Gem::Dependency
104
112
  name: rubocop-rspec
105
113
  requirement: !ruby/object:Gem::Requirement
@@ -154,10 +162,12 @@ files:
154
162
  - lib/jsonapi/query_builder.rb
155
163
  - lib/jsonapi/query_builder/base_filter.rb
156
164
  - lib/jsonapi/query_builder/base_query.rb
157
- - lib/jsonapi/query_builder/mixins/filtering.rb
165
+ - lib/jsonapi/query_builder/base_sort.rb
166
+ - lib/jsonapi/query_builder/mixins/filter.rb
158
167
  - lib/jsonapi/query_builder/mixins/include.rb
159
168
  - lib/jsonapi/query_builder/mixins/paginate.rb
160
169
  - lib/jsonapi/query_builder/mixins/sort.rb
170
+ - lib/jsonapi/query_builder/mixins/sort/param.rb
161
171
  - lib/jsonapi/query_builder/version.rb
162
172
  homepage: https://github.com/infinum/jsonapi-query_builder
163
173
  licenses:
@@ -177,9 +187,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
177
187
  version: '2.5'
178
188
  required_rubygems_version: !ruby/object:Gem::Requirement
179
189
  requirements:
180
- - - ">"
190
+ - - ">="
181
191
  - !ruby/object:Gem::Version
182
- version: 1.3.1
192
+ version: '0'
183
193
  requirements: []
184
194
  rubygems_version: 3.0.3
185
195
  signing_key:
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Jsonapi
4
- module QueryBuilder
5
- module Mixins
6
- module Filtering
7
- extend ActiveSupport::Concern
8
-
9
- class_methods do
10
- def supported_filters
11
- @supported_filters || {}
12
- end
13
-
14
- def filters_by(attribute, filter = nil, **options)
15
- filter ||= ->(collection, query) { collection.where(attribute => query) }
16
- @supported_filters = {**supported_filters, attribute => [filter, options]}
17
- end
18
- end
19
-
20
- def filter(collection, filter_params = send(:filter_params))
21
- self.class.supported_filters.reduce(collection) do |filtered_collection, supported_filter|
22
- filter, options = serialize_filter(supported_filter, collection: filtered_collection, params: filter_params)
23
-
24
- next filtered_collection unless options[:conditions].all? { |type, condition|
25
- check_condition(condition, type, filter: filter, query: options[:query])
26
- }
27
-
28
- filter.respond_to?(:call) ? filter.call(filtered_collection, options[:query]) : filter.results
29
- end
30
- end
31
-
32
- private
33
-
34
- def filter_params
35
- params[:filter] || {}
36
- end
37
-
38
- def serialize_filter(supported_filter, collection:, params:)
39
- attribute, (filter, options) = *supported_filter
40
-
41
- options[:query_parameter] = options[:query_parameter]&.to_sym || attribute
42
- options[:query] = params[options[:query_parameter]].presence
43
- options[:conditions] = options.slice(:if, :unless).presence ||
44
- {if: options[:query].present? || options[:allow_nil] && params.key?(options[:query_parameter])}
45
-
46
- filter = filter.new(collection, options[:query]) unless filter.respond_to?(:call)
47
-
48
- [filter, options]
49
- end
50
-
51
- def check_condition(condition, type, **opts)
52
- (type == :if) == case condition
53
- when Proc
54
- condition.call(opts[:query])
55
- when Symbol
56
- opts[:filter].send(condition)
57
- else
58
- condition
59
- end
60
- end
61
- end
62
- end
63
- end
64
- end