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 +4 -4
- data/VERSION +1 -0
- data/lib/rest_framework/controller_mixins/base.rb +15 -13
- data/lib/rest_framework/controller_mixins/models.rb +54 -25
- data/lib/rest_framework/errors.rb +2 -0
- data/lib/rest_framework/filters.rb +14 -6
- data/lib/rest_framework/routers.rb +4 -2
- data/lib/rest_framework/serializers.rb +4 -12
- data/lib/rest_framework/version.rb +18 -21
- metadata +3 -3
- data/lib/rest_framework/VERSION_STAMP +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a4c53fa557df9090c749745efce556214252b76e2115df09917970e9a9c8b5e
|
4
|
+
data.tar.gz: 5c33946ae697618f726532e9b45056f2e112d3f81f6efb5c9a37ab8d9b3b002e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
83
|
-
|
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
|
-
|
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({
|
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({
|
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
|
-
|
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
|
-
#
|
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
|
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!) &&
|
204
|
-
# Create with any properties inherited from the recordset
|
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.
|
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
|
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
|
-
|
44
|
+
ordering = {}
|
45
|
+
order_string.split(',').each do |field|
|
44
46
|
if field[0] == '-'
|
45
|
-
|
47
|
+
column = field[1..-1]
|
48
|
+
direction = :desc
|
46
49
|
else
|
47
|
-
|
50
|
+
column = field
|
51
|
+
direction = :asc
|
48
52
|
end
|
49
|
-
|
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
|
-
|
102
|
+
force_singular = kwargs.delete(:force_singular)
|
103
|
+
force_plural = kwargs.delete(:force_plural)
|
104
|
+
if force_singular
|
103
105
|
singular = true
|
104
|
-
elsif
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
12
|
-
|
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
|
13
|
+
# Git failed or was skipped, so try to find a VERSION file.
|
23
14
|
begin
|
24
|
-
version = File.read(
|
25
|
-
unless version
|
26
|
-
|
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
|
32
|
-
return '
|
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.
|
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-
|
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
|