rest_framework 0.2.4 → 0.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
  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