jsonapi.rb 1.2.1 → 1.3.0

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