jsonapi-query_builder 0.1.6.pre → 0.1.7

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
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