jsonapi.rb 1.2.1 → 1.3.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
2
  SHA1:
3
- metadata.gz: 33681c6b51c54e27169bfef6b0d9185e1fb80b95
4
- data.tar.gz: 4fabb406e5d206e9c42896c6e58a5e77372a68a7
3
+ metadata.gz: 84c4daefc4822fcbaf912d9bbf500fa8db752bc2
4
+ data.tar.gz: 3a338f497562d61ee01ccdb889fa309ccc2ebbdf
5
5
  SHA512:
6
- metadata.gz: ae9a1760b380ef3ff3ebb6b050cdb29b7e59e1032ad21d61f26bb97a357b873590f9fa12fca544cd7074ee04fab2d6ea40b86d7eccaa5832bd6ad334eacbe212
7
- data.tar.gz: d55f34067318a8fb16a841cf742b5e9180c306ca54e48f7f69461db45ad1d0df621f93c41d36727d644e38262973e420aad2253a132dee22295dd4a43ac0c4b0
6
+ metadata.gz: 0da896b2305034b5f448b440db0b93ec67155fdfc707c535328c045c679686b113de9511dd5d9761cf1cced8230776ab47ed85914258440a5b7d76e15e203254
7
+ data.tar.gz: 8f49760c1227c2017b74106fd3b47b64382aa46892114b54ba4db525fabe384ce6fd49ebe87bfdee69afbdec0aa0263ae39b249b0ceed78b2c2643f5fdde8088
@@ -8,7 +8,7 @@ GIT
8
8
  PATH
9
9
  remote: .
10
10
  specs:
11
- jsonapi.rb (1.2.1)
11
+ jsonapi.rb (1.3.0)
12
12
  fast_jsonapi (~> 1.5)
13
13
  rack
14
14
  ransack
data/README.md CHANGED
@@ -195,6 +195,18 @@ $ curl -X GET \
195
195
  &sort=-model_attr,relationship_attr
196
196
  ```
197
197
 
198
+ #### Sorting using expressions
199
+
200
+ You can use basic aggregations like `min`, `max`, `avg`, `sum` and `count`
201
+ when sorting. This is an optional feature since SQL aggregations require
202
+ grouping. To enable expressions along with filters, use the option flags:
203
+
204
+ ```ruby
205
+ options = { sort_with_expressions: true }
206
+ jsonapi_filter(User.all, allowed_fields, options) do |filtered|
207
+ render jsonapi: result.group('id').to_a
208
+ end
209
+ ```
198
210
 
199
211
  ### Pagination
200
212
 
@@ -1,8 +1,21 @@
1
1
  require 'ransack/predicate'
2
+ require_relative 'patches'
2
3
 
3
4
  # Filtering and sorting support
4
5
  module JSONAPI
5
6
  module Filtering
7
+ # Parses and returns the attribute and the predicate of a ransack field
8
+ #
9
+ # @param requested_field [String] the field to parse
10
+ # @return [Array] with the fields and the predicate
11
+ def self.extract_attributes_and_predicate(requested_field)
12
+ field_name = requested_field.to_s.dup
13
+ predicate = Ransack::Predicate.detect_and_strip_from_string!(field_name)
14
+ predicate = Ransack::Predicate.named(predicate)
15
+
16
+ [field_name.split(/_and_|_or_/), predicate]
17
+ end
18
+
6
19
  private
7
20
 
8
21
  # Applies filtering and sorting to a set of resources if requested
@@ -13,10 +26,12 @@ module JSONAPI
13
26
  # Ex.: `GET /resource?filter[region_matches_any]=Lisb%&sort=-created_at,id`
14
27
  #
15
28
  # @param allowed_fields [Array] a list of allowed fields to be filtered
29
+ # @param options [Hash] extra flags to enable/disable features
16
30
  # @return [ActiveRecord::Base] a collection of resources
17
- def jsonapi_filter(resources, allowed_fields)
31
+ def jsonapi_filter(resources, allowed_fields, options = {})
32
+ allowed_fields = allowed_fields.map(&:to_s)
18
33
  extracted_params = jsonapi_filter_params(allowed_fields)
19
- extracted_params[:sorts] = jsonapi_sort_params(allowed_fields)
34
+ extracted_params[:sorts] = jsonapi_sort_params(allowed_fields, options)
20
35
  resources = resources.ransack(extracted_params)
21
36
  block_given? ? yield(resources) : resources
22
37
  end
@@ -34,11 +49,8 @@ module JSONAPI
34
49
  allowed_fields = allowed_fields.map(&:to_s)
35
50
 
36
51
  requested.each_pair do |requested_field, to_filter|
37
- field_name = requested_field.dup
38
- predicate = Ransack::Predicate.detect_and_strip_from_string!(field_name)
39
- predicate = Ransack::Predicate.named(predicate)
40
-
41
- field_names = field_name.split(/_and_|_or_/)
52
+ field_names, predicate = JSONAPI::Filtering
53
+ .extract_attributes_and_predicate(requested_field)
42
54
 
43
55
  if to_filter.is_a?(String) && to_filter.include?(',')
44
56
  to_filter = to_filter.split(',')
@@ -52,24 +64,34 @@ module JSONAPI
52
64
  filtered
53
65
  end
54
66
 
55
- # Extracts and whitelists allowed fields to be sorted
67
+ # Extracts and whitelists allowed fields (or expressions) to be sorted
56
68
  #
57
69
  # @param allowed_fields [Array] a list of allowed fields to be sorted
70
+ # @param options [Hash] extra options to enable/disable features
58
71
  # @return [Hash] to be passed to [ActiveRecord::Base#order]
59
- def jsonapi_sort_params(allowed_fields)
72
+ def jsonapi_sort_params(allowed_fields, options = {})
73
+ filtered = []
60
74
  requested = params[:sort].to_s.split(',')
61
- requested.map! do |requested_field|
62
- desc = requested_field.to_s.start_with?('-')
63
- [
64
- desc ? requested_field[1..-1] : requested_field,
65
- desc ? 'desc' : 'asc'
66
- ]
67
- end
68
75
 
69
- # Convert to strings instead of hashes to allow joined table columns.
70
- requested.to_h.slice(*allowed_fields.map(&:to_s)).map do |field, dir|
71
- [field, dir].join(' ')
76
+ requested.each do |requested_field|
77
+ if requested_field.to_s.start_with?('-')
78
+ dir = 'desc'
79
+ requested_field = requested_field[1..-1]
80
+ else
81
+ dir = 'asc'
82
+ end
83
+
84
+ field_names, predicate = JSONAPI::Filtering
85
+ .extract_attributes_and_predicate(requested_field)
86
+
87
+ next unless (field_names - allowed_fields).empty?
88
+ next if !options[:sort_with_expressions] && predicate
89
+
90
+ # Convert to strings instead of hashes to allow joined table columns.
91
+ filtered << [requested_field, dir].join(' ')
72
92
  end
93
+
94
+ filtered
73
95
  end
74
96
  end
75
97
  end
@@ -0,0 +1,69 @@
1
+ require 'ransack'
2
+
3
+ Ransack.configure do |config|
4
+ # Raise errors if a query contains an unknown predicate or attribute.
5
+ # Default is true (do not raise error on unknown conditions).
6
+ config.ignore_unknown_conditions = true
7
+
8
+ # Enable expressions
9
+ # See: https://www.rubydoc.info/github/rails/rails/Arel/Expressions
10
+ config.add_predicate(
11
+ 'count', arel_predicate: 'count',
12
+ validator: ->(_) { true }, compounds: false
13
+ )
14
+ config.add_predicate(
15
+ 'count_distinct', arel_predicate: 'count',
16
+ validator: ->(_) { true }, formatter: ->(_) { true }, compounds: false
17
+ )
18
+ config.add_predicate(
19
+ 'sum', arel_predicate: 'sum',
20
+ validator: ->(v) { true }, compounds: false
21
+ )
22
+ config.add_predicate(
23
+ 'avg', arel_predicate: 'average',
24
+ validator: ->(v) { true }, compounds: false
25
+ )
26
+ config.add_predicate(
27
+ 'min', arel_predicate: 'minimum',
28
+ validator: ->(v) { true }, compounds: false
29
+ )
30
+ config.add_predicate(
31
+ 'max', arel_predicate: 'maximum',
32
+ validator: ->(v) { true }, compounds: false
33
+ )
34
+ end
35
+
36
+
37
+ Ransack::Visitor.class_eval do
38
+ alias_method :original_visit_Ransack_Nodes_Sort, :visit_Ransack_Nodes_Sort
39
+
40
+ private
41
+
42
+ # Original method assumes sorting is done only by attributes
43
+ def visit_Ransack_Nodes_Sort(node)
44
+ # Try the default sorting visitor method...
45
+ binded = original_visit_Ransack_Nodes_Sort(node)
46
+ valid = (binded.valid? if binded.respond_to?(:valid?)) || true
47
+ return binded if binded.present? && valid
48
+
49
+ # Fallback to support the expressions...
50
+ binded = Ransack::Nodes::Condition.extract(node.context, node.name, nil)
51
+ valid = (binded.valid? if binded.respond_to?(:valid?)) || true
52
+ binded.arel_predicate if binded.present? && valid
53
+ end
54
+ end
55
+
56
+ Ransack::Nodes::Condition.class_eval do
57
+ alias_method :original_format_predicate, :format_predicate
58
+
59
+ private
60
+
61
+ # Original method doesn't respect the arity of expressions
62
+ # See: lib/ransack/adapters/active_record/ransack/nodes/condition.rb#L30-L42
63
+ def format_predicate(attribute)
64
+ original_format_predicate(attribute)
65
+ rescue ArgumentError
66
+ arel_pred = arel_predicate_for_attribute(attribute)
67
+ attribute.attr.public_send(arel_pred)
68
+ end
69
+ end
@@ -1,3 +1,3 @@
1
1
  module JSONAPI
2
- VERSION = '1.2.1'
2
+ VERSION = '1.3.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stas Suscov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-17 00:00:00.000000000 Z
11
+ date: 2019-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fast_jsonapi
@@ -217,6 +217,7 @@ files:
217
217
  - lib/jsonapi/fetching.rb
218
218
  - lib/jsonapi/filtering.rb
219
219
  - lib/jsonapi/pagination.rb
220
+ - lib/jsonapi/patches.rb
220
221
  - lib/jsonapi/rails.rb
221
222
  - lib/jsonapi/version.rb
222
223
  homepage: https://github.com/stas/jsonapi.rb