hal_api-rails 0.5.0 → 0.6.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
- SHA256:
3
- metadata.gz: 0d17fee58879f4f95b3f32e05bd626d6242f9c2d1e321073ce48aa6c2cfb2e37
4
- data.tar.gz: f23c5f2dcee52f79701947acdb1ee6835b7ab864aaa606f065758a82617c0750
2
+ SHA1:
3
+ metadata.gz: 4f8ad6ad0044a8438881c35775259d46cce7448d
4
+ data.tar.gz: c4dd5e6a42a4cca9d744e7050a91dc6d06e335f9
5
5
  SHA512:
6
- metadata.gz: d96ae095dfa08377102ca42a3ad02294d04a44d37df600d69beb7e69428ec5621ad8e257dcca5a668f76ab19717c753cbe5e4b5bfc66b44830bf1821b10a7a9c
7
- data.tar.gz: a7889a43949e18dbf54796de43a15ce5f3868fcbf6711db4911b4f2e2d56278f8698b8bcdfd486af425904594da2be8ce9ec7f5978158bf8d66d8add2be1c2ef
6
+ metadata.gz: ae0cf7fc44f171df5972c660d732cbd837f89216e001ef76f8b9aa97a0cc25b8581e975a90efdfdee7401cc7e9ae3ad947442239119828a374806ac023b3ce3a
7
+ data.tar.gz: 9d8eb8563e870a584b7eb69696a8d205cbb03b5b6ea68b33c64913ced078a158cfc21a37fb62a0c349b2a66c4cb026a8e3448080139f27ec88bc939ebd6e0d12
@@ -9,12 +9,16 @@ module HalApi::Controller
9
9
  require 'hal_api/controller/cache'
10
10
  require 'hal_api/controller/resources'
11
11
  require 'hal_api/controller/exceptions'
12
+ require 'hal_api/controller/sorting'
13
+ require 'hal_api/controller/filtering'
12
14
  require 'hal_api/responders/api_responder'
13
15
 
14
16
  include HalApi::Controller::Actions
15
17
  include HalApi::Controller::Cache
16
18
  include HalApi::Controller::Resources
17
19
  include HalApi::Controller::Exceptions
20
+ include HalApi::Controller::Sorting
21
+ include HalApi::Controller::Filtering
18
22
 
19
23
  included do
20
24
  include Roar::Rails::ControllerAdditions
@@ -0,0 +1,107 @@
1
+ module HalApi::Controller::Filtering
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ class_eval do
6
+ class_attribute :allowed_filter_names
7
+ class_attribute :allowed_filter_types
8
+ end
9
+ end
10
+
11
+ class FilterParams < OpenStruct
12
+ def initialize(filters = {})
13
+ @filters = filters.with_indifferent_access
14
+ end
15
+
16
+ def method_missing(m, *args, &_block)
17
+ if @filters.key?(m) && args.empty?
18
+ @filters[m]
19
+ elsif m.to_s[-1] == '?' && args.empty? && @filters.key?(m.to_s.chop)
20
+ !!@filters[m.to_s.chop]
21
+ else
22
+ msg = "Unknown filter param '#{m}'"
23
+ hint = "Valid filters are: #{@filters.keys.join(' ')}"
24
+ raise HalApi::Errors::UnknownFilterError.new(msg, hint)
25
+ end
26
+ end
27
+ end
28
+
29
+ module ClassMethods
30
+ def filter_params(*args)
31
+ self.allowed_filter_names = []
32
+ self.allowed_filter_types = {}
33
+ (args || []).map do |arg|
34
+ if arg.is_a? Hash
35
+ arg.to_a.each { |key, val| add_filter_param(key.to_s, val.to_s) }
36
+ else
37
+ add_filter_param(arg.to_s)
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def add_filter_param(name, type = nil)
45
+ unless allowed_filter_names.include? name
46
+ allowed_filter_names << name
47
+ allowed_filter_types[name] = type unless type.nil?
48
+ end
49
+ end
50
+ end
51
+
52
+ def filters
53
+ @filters ||= parse_filters_param
54
+ end
55
+
56
+ private
57
+
58
+ def parse_filters_param
59
+ filters_map = {}
60
+ filters = self.class.allowed_filter_names
61
+ force_types = self.class.allowed_filter_types
62
+
63
+ # set nils
64
+ filters.each do |name|
65
+ filters_map[name] = nil
66
+ end
67
+
68
+ # parse query param
69
+ (params[:filters] || '').split(',').each do |str|
70
+ name, value = str.split('=', 2)
71
+ next unless filters_map.key?(name)
72
+
73
+ # convert/guess type of known params
74
+ filters_map[name] =
75
+ if force_types[name] == 'date'
76
+ parse_date(value)
77
+ elsif force_types[name] == 'time'
78
+ parse_time(value)
79
+ elsif value.nil?
80
+ true
81
+ elsif value.blank?
82
+ ''
83
+ elsif [false, 'false'].include? value
84
+ false
85
+ elsif [true, 'true'].include? value
86
+ true
87
+ elsif value =~ /\A[-+]?\d+\z/
88
+ value.to_i
89
+ else
90
+ value
91
+ end
92
+ end
93
+ FilterParams.new(filters_map)
94
+ end
95
+
96
+ def parse_date(str)
97
+ Date.parse(str)
98
+ rescue ArgumentError
99
+ raise HalApi::Errors::BadFilterValueError.new "Invalid filter date: '#{str}'"
100
+ end
101
+
102
+ def parse_time(str)
103
+ Time.find_zone('UTC').parse(str) || (raise ArgumentError.new 'Nil result!')
104
+ rescue ArgumentError
105
+ raise HalApi::Errors::BadFilterValueError.new "Invalid filter time: '#{str}'"
106
+ end
107
+ end
@@ -0,0 +1,61 @@
1
+ module HalApi::Controller::Sorting
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ class_eval do
6
+ class_attribute :allowed_sort_names
7
+ class_attribute :default_sort
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ def sort_params(args)
13
+ self.allowed_sort_names = args[:allowed].map(&:to_s).uniq
14
+ self.default_sort = args[:default]
15
+ if default_sort && !default_sort.is_a?(Array)
16
+ self.default_sort = Array[default_sort]
17
+ end
18
+ end
19
+ end
20
+
21
+ def sorts
22
+ @sorts ||= parse_sorts_param
23
+ end
24
+
25
+ def sorted(arel)
26
+ apply_sorts = !sorts.blank? ? sorts : default_sort
27
+ if apply_sorts.blank?
28
+ super
29
+ else
30
+ arel.order(*apply_sorts)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ # support ?sorts=attribute,attribute:direction params
37
+ # e.g. ?sorts=published_at,updated_at:desc
38
+ # desc is default if a direction is not specified
39
+ def parse_sorts_param
40
+ sorts_array = []
41
+ allowed_sorts = self.class.allowed_sort_names
42
+
43
+ # parse sort param for name of the column and direction
44
+ # default is descending, because I say so, and we have a bias towards the new
45
+ (params[:sorts] || '').split(',').each do |str|
46
+ name, direction = str.split(':', 2).map { |s| s.to_s.strip }
47
+ name = name.underscore
48
+ direction = direction.blank? ? 'desc' : direction.downcase
49
+ unless allowed_sorts.include?(name)
50
+ hint = "Valid sorts are: #{allowed_sorts.join(' ')}"
51
+ raise HalApi::Errors::BadSortError.new("Invalid sort: #{name}", hint)
52
+ end
53
+ unless ['asc', 'desc'].include?(direction)
54
+ hint = "Valid directions are: asc desc"
55
+ raise HalApi::Errors::BadSortError.new("Invalid sort direction: #{direction}", hint)
56
+ end
57
+ sorts_array << { name => direction }
58
+ end
59
+ sorts_array
60
+ end
61
+ end
@@ -4,10 +4,18 @@ module HalApi::Errors
4
4
 
5
5
  class ApiError < StandardError
6
6
  attr_accessor :status
7
+ attr_accessor :hint
7
8
 
8
- def initialize(message = nil, status = 500)
9
+ def initialize(message = nil, status = nil, hint = nil)
9
10
  super(message || "API Error")
10
- self.status = status
11
+ self.status = status || 500
12
+ self.hint = hint
13
+ end
14
+ end
15
+
16
+ class Forbidden < ApiError
17
+ def initialize(message = nil, hint = nil)
18
+ super(message || 'Forbidden', 403, hint)
11
19
  end
12
20
  end
13
21
 
@@ -23,6 +31,24 @@ module HalApi::Errors
23
31
  end
24
32
  end
25
33
 
34
+ class BadSortError < ApiError
35
+ def initialize(msg, hint = nil)
36
+ super(msg, 400, hint)
37
+ end
38
+ end
39
+
40
+ class UnknownFilterError < ApiError
41
+ def initialize(msg, hint = nil)
42
+ super(msg, 400, hint)
43
+ end
44
+ end
45
+
46
+ class BadFilterValueError < ApiError
47
+ def initialize(msg, hint = nil)
48
+ super(msg, 400, hint)
49
+ end
50
+ end
51
+
26
52
  module Representer
27
53
  include Roar::JSON::HAL
28
54
 
@@ -1,5 +1,5 @@
1
1
  module HalApi
2
2
  module Rails
3
- VERSION = "0.5.0"
3
+ VERSION = "0.6.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hal_api-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Rhoden
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-08-13 00:00:00.000000000 Z
12
+ date: 2019-08-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel
@@ -216,7 +216,9 @@ files:
216
216
  - lib/hal_api/controller/actions.rb
217
217
  - lib/hal_api/controller/cache.rb
218
218
  - lib/hal_api/controller/exceptions.rb
219
+ - lib/hal_api/controller/filtering.rb
219
220
  - lib/hal_api/controller/resources.rb
221
+ - lib/hal_api/controller/sorting.rb
220
222
  - lib/hal_api/errors.rb
221
223
  - lib/hal_api/paged_collection.rb
222
224
  - lib/hal_api/paged_collection_representer.rb
@@ -253,7 +255,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
253
255
  version: '0'
254
256
  requirements: []
255
257
  rubyforge_project:
256
- rubygems_version: 2.7.6.2
258
+ rubygems_version: 2.5.2.3
257
259
  signing_key:
258
260
  specification_version: 4
259
261
  summary: JSON HAL APIs on Rails in the style of PRX