rest_framework 0.2.4 → 0.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
  SHA256:
3
- metadata.gz: 8d9ce4741f59e6f68bfa18bc2e6c11202c91fbe251eeaa5562f8f718e6e8afaa
4
- data.tar.gz: 652ad42a8d0bad99e552af9117112df707e67d4c1e6dd2dd983e7925933fcab6
3
+ metadata.gz: 5a4c53fa557df9090c749745efce556214252b76e2115df09917970e9a9c8b5e
4
+ data.tar.gz: 5c33946ae697618f726532e9b45056f2e112d3f81f6efb5c9a37ab8d9b3b002e
5
5
  SHA512:
6
- metadata.gz: 26036468d40ccc77a203981ba442265ef82c7a991fee800ee3d7925d96d09a696f3db06930e8252c49cf63e86d2aee2fd121a735e5e06609585299ff984d6992
7
- data.tar.gz: 3c53d8eed996e380ab3390acfde7a3cef262d38a1355d0016aa6d8129b8e329c99003c0181ec1602f5bb1ba8869355e6b1eb0ff366d8b814ff83f6bb3c9cbf4d
6
+ metadata.gz: ddfd6544b96284e871684d2f448e622631bd88631ea78ebe4170d2ff2fe4f1654901ef86a96584e3c5d0a2538bcef5d29f85152795aa689df58fc98cb05f7b3b
7
+ data.tar.gz: 1ab60515fcf543c0df574a6cf3913648736d19d559b1d9fbe55f891ce3e6926026357369580430ada33b6e294a081d8058b297bb3f146b3d17f4671b10683d55
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
@@ -79,8 +79,12 @@ module RESTFramework::BaseControllerMixin
79
79
 
80
80
  protected
81
81
 
82
- # Helper to get filtering backends with a sane default.
83
- # @return [RESTFramework::BaseFilter]
82
+ # Helper to get the configured serializer class.
83
+ def get_serializer_class
84
+ return self.class.serializer_class
85
+ end
86
+
87
+ # Helper to get filtering backends, defaulting to no backends.
84
88
  def get_filter_backends
85
89
  return self.class.filter_backends || []
86
90
  end
@@ -95,16 +99,10 @@ module RESTFramework::BaseControllerMixin
95
99
  return data
96
100
  end
97
101
 
98
- # Helper to get the configured serializer class.
99
- # @return [RESTFramework::BaseSerializer]
100
- def get_serializer_class
101
- return self.class.serializer_class
102
- end
103
-
104
102
  def record_invalid(e)
105
- return api_response(
106
- {message: "Record invalid.", exception: e, errors: e.record.errors}, status: 400
107
- )
103
+ return api_response({
104
+ message: "Record invalid.", exception: e, errors: e.record&.errors
105
+ }, status: 400)
108
106
  end
109
107
 
110
108
  def record_not_found(e)
@@ -112,11 +110,15 @@ module RESTFramework::BaseControllerMixin
112
110
  end
113
111
 
114
112
  def record_not_saved(e)
115
- return api_response({message: "Record not saved.", exception: e}, status: 406)
113
+ return api_response({
114
+ message: "Record not saved.", exception: e, errors: e.record&.errors
115
+ }, status: 406)
116
116
  end
117
117
 
118
118
  def record_not_destroyed(e)
119
- return api_response({message: "Record not destroyed.", exception: e}, status: 406)
119
+ return api_response({
120
+ message: "Record not destroyed.", exception: e, errors: e.record&.errors
121
+ }, status: 406)
120
122
  end
121
123
 
122
124
  # Helper for showing routes under a controller action, used for the browsable API.
@@ -20,6 +20,10 @@ module RESTFramework::BaseModelControllerMixin
20
20
  fields: nil,
21
21
  action_fields: nil,
22
22
 
23
+ # Attributes for finding records.
24
+ find_by_fields: nil,
25
+ find_by_query_param: 'find_by',
26
+
23
27
  # Attributes for create/update parameters.
24
28
  allowed_parameters: nil,
25
29
  allowed_action_parameters: nil,
@@ -35,7 +39,8 @@ module RESTFramework::BaseModelControllerMixin
35
39
  ordering_query_param: 'ordering',
36
40
 
37
41
  # Other misc attributes.
38
- disable_creation_from_recordset: false, # Option to disable `recordset.create` behavior.
42
+ create_from_recordset: true, # Option for `recordset.create` vs `Model.create` behavior.
43
+ filter_recordset_before_find: true, # Option to control if filtering is done before find.
39
44
  }.each do |a, default|
40
45
  unless base.respond_to?(a)
41
46
  base.class_attribute(a)
@@ -60,11 +65,6 @@ module RESTFramework::BaseModelControllerMixin
60
65
  return (action_config[action] if action) || self.class.send(generic_config_key)
61
66
  end
62
67
 
63
- # Get a list of parameters allowed for the current action.
64
- def get_allowed_parameters
65
- return _get_specific_action_config(:allowed_action_parameters, :allowed_parameters)&.map(&:to_s)
66
- end
67
-
68
68
  # Get a list of fields for the current action.
69
69
  def get_fields
70
70
  return (
@@ -74,14 +74,32 @@ module RESTFramework::BaseModelControllerMixin
74
74
  )
75
75
  end
76
76
 
77
+ # Get a list of find_by fields for the current action.
78
+ def get_find_by_fields
79
+ return self.class.find_by_fields&.map(&:to_s) || self.get_fields
80
+ end
81
+
82
+ # Get a list of find_by fields for the current action.
83
+ def get_filterset_fields
84
+ return self.class.filterset_fields&.map(&:to_s) || self.get_fields
85
+ end
86
+
87
+ # Get a list of ordering fields for the current action.
88
+ def get_ordering_fields
89
+ return self.class.ordering_fields&.map(&:to_s) || self.get_fields
90
+ end
91
+
92
+ # Get a list of parameters allowed for the current action.
93
+ def get_allowed_parameters
94
+ return _get_specific_action_config(:allowed_action_parameters, :allowed_parameters)&.map(&:to_s)
95
+ end
96
+
77
97
  # Helper to get the configured serializer class, or `NativeSerializer` as a default.
78
- # @return [RESTFramework::BaseSerializer]
79
98
  def get_serializer_class
80
99
  return self.class.serializer_class || RESTFramework::NativeSerializer
81
100
  end
82
101
 
83
- # Get the list of filtering backends to use.
84
- # @return [RESTFramework::BaseFilter]
102
+ # Helper to get filtering backends, defaulting to using `ModelFilter` and `ModelOrderingFilter`.
85
103
  def get_filter_backends
86
104
  return self.class.filter_backends || [
87
105
  RESTFramework::ModelFilter, RESTFramework::ModelOrderingFilter
@@ -94,9 +112,7 @@ module RESTFramework::BaseModelControllerMixin
94
112
  fields = self.get_allowed_parameters || self.get_fields
95
113
 
96
114
  # Filter the request body.
97
- body_params = request.request_parameters.select { |p|
98
- fields.include?(p)
99
- }
115
+ body_params = request.request_parameters.select { |p| fields.include?(p) }
100
116
 
101
117
  # Add query params in place of missing body params, if configured.
102
118
  if self.class.accept_generic_params_as_body_params
@@ -113,9 +129,7 @@ module RESTFramework::BaseModelControllerMixin
113
129
  end
114
130
 
115
131
  # Filter fields in exclude_body_fields.
116
- (self.class.exclude_body_fields || []).each do |f|
117
- body_params.delete(f.to_s)
118
- end
132
+ (self.class.exclude_body_fields || []).each { |f| body_params.delete(f.to_s) }
119
133
 
120
134
  body_params
121
135
  end
@@ -123,14 +137,6 @@ module RESTFramework::BaseModelControllerMixin
123
137
  alias :get_create_params :get_body_params
124
138
  alias :get_update_params :get_body_params
125
139
 
126
- # Get a record by the primary key from the (non-filtered) recordset.
127
- def get_record
128
- if pk = params[self.get_model.primary_key]
129
- return self.get_recordset.find(pk)
130
- end
131
- return nil
132
- end
133
-
134
140
  # Get the model for this controller.
135
141
  def get_model(from_get_recordset: false)
136
142
  return @model if instance_variable_defined?(:@model) && @model
@@ -164,6 +170,29 @@ module RESTFramework::BaseModelControllerMixin
164
170
 
165
171
  return nil
166
172
  end
173
+
174
+ # Get a single record by primary key or another column, if allowed.
175
+ def get_record
176
+ recordset = self.get_recordset
177
+ find_by_fields = self.get_find_by_fields
178
+ find_by_key = self.get_model.primary_key
179
+
180
+ # Find by another column if it's permitted.
181
+ if find_by_query_param && params[find_by_query_param].in?(find_by_fields)
182
+ find_by_key = params[find_by_query_param]
183
+ end
184
+
185
+ # Filter recordset, if configured.
186
+ if self.filter_recordset_before_find
187
+ recordset = self.get_filtered_data(recordset)
188
+ end
189
+
190
+ # Return the record.
191
+ if find_by_value = params[:id] # Route key is always :id by Rails convention.
192
+ return self.get_recordset.find_by!(find_by_key => find_by_value)
193
+ end
194
+ return nil
195
+ end
167
196
  end
168
197
 
169
198
 
@@ -200,8 +229,8 @@ end
200
229
  # Mixin for creating records.
201
230
  module RESTFramework::CreateModelMixin
202
231
  def create
203
- if self.get_recordset.respond_to?(:create!) && !self.disable_creation_from_recordset
204
- # Create with any properties inherited from the recordset (like associations).
232
+ if self.get_recordset.respond_to?(:create!) && self.create_from_recordset
233
+ # Create with any properties inherited from the recordset.
205
234
  @record = self.get_recordset.create!(self.get_create_params)
206
235
  else
207
236
  # Otherwise, perform a "bare" create.
@@ -2,6 +2,7 @@
2
2
  class RESTFramework::Error < StandardError
3
3
  end
4
4
 
5
+
5
6
  class RESTFramework::NilPassedToAPIResponseError < RESTFramework::Error
6
7
  def message
7
8
  return <<~MSG.split("\n").join(' ')
@@ -14,6 +15,7 @@ class RESTFramework::NilPassedToAPIResponseError < RESTFramework::Error
14
15
  end
15
16
  end
16
17
 
18
+
17
19
  class RESTFramework::UnserializableError < RESTFramework::Error
18
20
  def initialize(object)
19
21
  @object = object
@@ -14,7 +14,7 @@ end
14
14
  class RESTFramework::ModelFilter < RESTFramework::BaseFilter
15
15
  # Filter params for keys allowed by the current action's filterset_fields/fields config.
16
16
  def _get_filter_params
17
- fields = @controller.class.filterset_fields&.map(&:to_s) || @controller.send(:get_fields)
17
+ fields = @controller.send(:get_filterset_fields)
18
18
  return @controller.request.query_parameters.select { |p|
19
19
  fields.include?(p)
20
20
  }.to_h.symbolize_keys # convert from HashWithIndifferentAccess to Hash w/keys
@@ -36,17 +36,25 @@ end
36
36
  class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
37
37
  # Convert ordering string to an ordering configuration.
38
38
  def _get_ordering
39
- return nil unless @controller.class.ordering_query_param
39
+ return nil if @controller.class.ordering_query_param.blank?
40
+ ordering_fields = @controller.send(:get_ordering_fields)
40
41
 
41
42
  order_string = @controller.params[@controller.class.ordering_query_param]
42
43
  unless order_string.blank?
43
- return order_string.split(',').map { |field|
44
+ ordering = {}
45
+ order_string.split(',').each do |field|
44
46
  if field[0] == '-'
45
- [field[1..-1].to_sym, :desc]
47
+ column = field[1..-1]
48
+ direction = :desc
46
49
  else
47
- [field.to_sym, :asc]
50
+ column = field
51
+ direction = :asc
48
52
  end
49
- }.to_h
53
+ if column.in?(ordering_fields)
54
+ ordering[column.to_sym] = direction
55
+ end
56
+ end
57
+ return ordering
50
58
  end
51
59
 
52
60
  return nil
@@ -99,9 +99,11 @@ module ActionDispatch::Routing
99
99
  kwargs[:controller] = name unless kwargs[:controller]
100
100
 
101
101
  # determine plural/singular resource
102
- if kwargs.delete(:force_singular)
102
+ force_singular = kwargs.delete(:force_singular)
103
+ force_plural = kwargs.delete(:force_plural)
104
+ if force_singular
103
105
  singular = true
104
- elsif kwargs.delete(:force_plural)
106
+ elsif force_plural
105
107
  singular = false
106
108
  elsif !controller_class.singleton_controller.nil?
107
109
  singular = controller_class.singleton_controller
@@ -121,29 +121,21 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
121
121
 
122
122
  # Allow a serializer instance to be used as a hash directly in a nested serializer config.
123
123
  def [](key)
124
- unless instance_variable_defined?(:@_nested_config)
125
- @_nested_config = self.get_serializer_config
126
- end
124
+ @_nested_config ||= self.get_serializer_config
127
125
  return @_nested_config[key]
128
126
  end
129
127
  def []=(key, value)
130
- unless instance_variable_defined?(:@_nested_config)
131
- @_nested_config = self.get_serializer_config
132
- end
128
+ @_nested_config ||= self.get_serializer_config
133
129
  return @_nested_config[key] = value
134
130
  end
135
131
 
136
132
  # Allow a serializer class to be used as a hash directly in a nested serializer config.
137
133
  def self.[](key)
138
- unless instance_variable_defined?(:@_nested_config)
139
- @_nested_config = self.new.get_serializer_config
140
- end
134
+ @_nested_config ||= self.new.get_serializer_config
141
135
  return @_nested_config[key]
142
136
  end
143
137
  def self.[]=(key, value)
144
- unless instance_variable_defined?(:@_nested_config)
145
- @_nested_config = self.new.get_serializer_config
146
- end
138
+ @_nested_config ||= self.new.get_serializer_config
147
139
  return @_nested_config[key] = value
148
140
  end
149
141
  end
@@ -1,35 +1,32 @@
1
+ # Do not use Rails-specific helper methods here (e.g., `blank?`) so the module can run standalone.
1
2
  module RESTFramework
2
3
  module Version
3
- @_version = nil
4
+ VERSION_FILEPATH = File.expand_path("../../VERSION", __dir__)
4
5
 
5
6
  def self.get_version(skip_git: false)
6
- # Return cached @_version, if available.
7
- return @_version if @_version
8
-
9
7
  # First, attempt to get the version from git.
10
8
  unless skip_git
11
- begin
12
- version = `git describe 2>/dev/null`.strip
13
- raise "blank version" if version.nil? || version.match(/^\w*$/)
14
- # Check for local changes.
15
- changes = `git status --porcelain 2>/dev/null`
16
- version << '.localchanges' if changes.strip.length > 0
17
- return version
18
- rescue
19
- end
9
+ version = `git describe --dirty --broken 2>/dev/null`&.strip
10
+ return version unless !version || version.empty?
20
11
  end
21
12
 
22
- # Git failed, so try to find a VERSION_STAMP.
13
+ # Git failed or was skipped, so try to find a VERSION file.
23
14
  begin
24
- version = File.read(File.expand_path("VERSION_STAMP", __dir__))
25
- unless version.nil? || version.match(/^\w*$/)
26
- return (@_version = version) # cache VERSION_STAMP content
27
- end
28
- rescue
15
+ version = File.read(VERSION_FILEPATH)&.strip
16
+ return version unless !version || version.blank?
17
+ rescue SystemCallError
29
18
  end
30
19
 
31
- # No VERSION_STAMP, so version is unknown.
32
- return '0.unknown'
20
+ # No VERSION file, so version is unknown.
21
+ return 'unknown'
22
+ end
23
+
24
+ def self.stamp_version
25
+ File.write(VERSION_FILEPATH, RESTFramework::VERSION)
26
+ end
27
+
28
+ def self.unstamp_version
29
+ File.delete(VERSION_FILEPATH) if File.exist?(VERSION_FILEPATH)
33
30
  end
34
31
  end
35
32
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rest_framework
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gregory N. Schmit
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-29 00:00:00.000000000 Z
11
+ date: 2021-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -33,12 +33,12 @@ extra_rdoc_files: []
33
33
  files:
34
34
  - LICENSE
35
35
  - README.md
36
+ - VERSION
36
37
  - app/views/layouts/rest_framework.html.erb
37
38
  - app/views/rest_framework/_head.html.erb
38
39
  - app/views/rest_framework/_routes.html.erb
39
40
  - app/views/rest_framework/default.html.erb
40
41
  - lib/rest_framework.rb
41
- - lib/rest_framework/VERSION_STAMP
42
42
  - lib/rest_framework/controller_mixins.rb
43
43
  - lib/rest_framework/controller_mixins/base.rb
44
44
  - lib/rest_framework/controller_mixins/models.rb
@@ -1 +0,0 @@
1
- 0.2.4