rest_framework 0.2.4 → 0.3.5
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 +17 -15
- data/lib/rest_framework/controller_mixins/models.rb +63 -25
- data/lib/rest_framework/errors.rb +2 -0
- data/lib/rest_framework/filters.rb +37 -12
- data/lib/rest_framework/paginators.rb +1 -0
- 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 +4 -5
- data/app/views/rest_framework/default.html.erb +0 -0
- 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: dc5065eb969e4616e6a0e22717a4398940b3696a43182d6e8b526e225f6c81f3
|
4
|
+
data.tar.gz: 3efcfdfa6abfe7343770c8241e83d34bc1e02bcbd145826bc8a9709514b8b5f7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 170b7b9ebb18b9527d1e9216a0456af154284ab8935d1ddcc367815bf73c86379ae83340f6a259f6b4324d5c4cf32eae740a28210a1ce963427ef96a870ae016
|
7
|
+
data.tar.gz: a6148d463fa2eda9dc6f19dc89646a78b922a93bdcf6f147707343d218a9501e8bebe67d8182d75a946968d59b27a0f030d08349268297799bc4886b780f6aa4
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.5
|
@@ -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.
|
@@ -180,9 +182,9 @@ module RESTFramework::BaseControllerMixin
|
|
180
182
|
hkwargs = kwargs.merge(html_kwargs)
|
181
183
|
begin
|
182
184
|
render(**hkwargs)
|
183
|
-
rescue ActionView::MissingTemplate # fallback to rest_framework layout
|
185
|
+
rescue ActionView::MissingTemplate # fallback to rest_framework layout
|
184
186
|
hkwargs[:layout] = "rest_framework"
|
185
|
-
hkwargs[:
|
187
|
+
hkwargs[:html] = ''
|
186
188
|
render(**hkwargs)
|
187
189
|
end
|
188
190
|
}
|
@@ -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,
|
@@ -33,9 +37,14 @@ module RESTFramework::BaseModelControllerMixin
|
|
33
37
|
filterset_fields: nil,
|
34
38
|
ordering_fields: nil,
|
35
39
|
ordering_query_param: 'ordering',
|
40
|
+
ordering_no_reorder: false,
|
41
|
+
search_fields: nil,
|
42
|
+
search_query_param: 'search',
|
43
|
+
search_ilike: false,
|
36
44
|
|
37
45
|
# Other misc attributes.
|
38
|
-
|
46
|
+
create_from_recordset: true, # Option for `recordset.create` vs `Model.create` behavior.
|
47
|
+
filter_recordset_before_find: true, # Option to control if filtering is done before find.
|
39
48
|
}.each do |a, default|
|
40
49
|
unless base.respond_to?(a)
|
41
50
|
base.class_attribute(a)
|
@@ -60,11 +69,6 @@ module RESTFramework::BaseModelControllerMixin
|
|
60
69
|
return (action_config[action] if action) || self.class.send(generic_config_key)
|
61
70
|
end
|
62
71
|
|
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
72
|
# Get a list of fields for the current action.
|
69
73
|
def get_fields
|
70
74
|
return (
|
@@ -74,14 +78,37 @@ module RESTFramework::BaseModelControllerMixin
|
|
74
78
|
)
|
75
79
|
end
|
76
80
|
|
81
|
+
# Get a list of find_by fields for the current action.
|
82
|
+
def get_find_by_fields
|
83
|
+
return self.class.find_by_fields&.map(&:to_s) || self.get_fields
|
84
|
+
end
|
85
|
+
|
86
|
+
# Get a list of find_by fields for the current action.
|
87
|
+
def get_filterset_fields
|
88
|
+
return self.class.filterset_fields&.map(&:to_s) || self.get_fields
|
89
|
+
end
|
90
|
+
|
91
|
+
# Get a list of ordering fields for the current action.
|
92
|
+
def get_ordering_fields
|
93
|
+
return self.class.ordering_fields&.map(&:to_s) || self.get_fields
|
94
|
+
end
|
95
|
+
|
96
|
+
# Get a list of search fields for the current action.
|
97
|
+
def get_search_fields
|
98
|
+
return self.class.search_fields&.map(&:to_s) || self.get_fields
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get a list of parameters allowed for the current action.
|
102
|
+
def get_allowed_parameters
|
103
|
+
return _get_specific_action_config(:allowed_action_parameters, :allowed_parameters)&.map(&:to_s)
|
104
|
+
end
|
105
|
+
|
77
106
|
# Helper to get the configured serializer class, or `NativeSerializer` as a default.
|
78
|
-
# @return [RESTFramework::BaseSerializer]
|
79
107
|
def get_serializer_class
|
80
108
|
return self.class.serializer_class || RESTFramework::NativeSerializer
|
81
109
|
end
|
82
110
|
|
83
|
-
#
|
84
|
-
# @return [RESTFramework::BaseFilter]
|
111
|
+
# Helper to get filtering backends, defaulting to using `ModelFilter` and `ModelOrderingFilter`.
|
85
112
|
def get_filter_backends
|
86
113
|
return self.class.filter_backends || [
|
87
114
|
RESTFramework::ModelFilter, RESTFramework::ModelOrderingFilter
|
@@ -94,9 +121,7 @@ module RESTFramework::BaseModelControllerMixin
|
|
94
121
|
fields = self.get_allowed_parameters || self.get_fields
|
95
122
|
|
96
123
|
# Filter the request body.
|
97
|
-
body_params = request.request_parameters.select { |p|
|
98
|
-
fields.include?(p)
|
99
|
-
}
|
124
|
+
body_params = request.request_parameters.select { |p| fields.include?(p) }
|
100
125
|
|
101
126
|
# Add query params in place of missing body params, if configured.
|
102
127
|
if self.class.accept_generic_params_as_body_params
|
@@ -113,9 +138,7 @@ module RESTFramework::BaseModelControllerMixin
|
|
113
138
|
end
|
114
139
|
|
115
140
|
# Filter fields in exclude_body_fields.
|
116
|
-
(self.class.exclude_body_fields || []).each
|
117
|
-
body_params.delete(f.to_s)
|
118
|
-
end
|
141
|
+
(self.class.exclude_body_fields || []).each { |f| body_params.delete(f.to_s) }
|
119
142
|
|
120
143
|
body_params
|
121
144
|
end
|
@@ -123,14 +146,6 @@ module RESTFramework::BaseModelControllerMixin
|
|
123
146
|
alias :get_create_params :get_body_params
|
124
147
|
alias :get_update_params :get_body_params
|
125
148
|
|
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
149
|
# Get the model for this controller.
|
135
150
|
def get_model(from_get_recordset: false)
|
136
151
|
return @model if instance_variable_defined?(:@model) && @model
|
@@ -164,6 +179,29 @@ module RESTFramework::BaseModelControllerMixin
|
|
164
179
|
|
165
180
|
return nil
|
166
181
|
end
|
182
|
+
|
183
|
+
# Get a single record by primary key or another column, if allowed.
|
184
|
+
def get_record
|
185
|
+
recordset = self.get_recordset
|
186
|
+
find_by_fields = self.get_find_by_fields
|
187
|
+
find_by_key = self.get_model.primary_key
|
188
|
+
|
189
|
+
# Find by another column if it's permitted.
|
190
|
+
if find_by_query_param && params[find_by_query_param].in?(find_by_fields)
|
191
|
+
find_by_key = params[find_by_query_param]
|
192
|
+
end
|
193
|
+
|
194
|
+
# Filter recordset, if configured.
|
195
|
+
if self.filter_recordset_before_find
|
196
|
+
recordset = self.get_filtered_data(recordset)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Return the record.
|
200
|
+
if find_by_value = params[:id] # Route key is always :id by Rails convention.
|
201
|
+
return self.get_recordset.find_by!(find_by_key => find_by_value)
|
202
|
+
end
|
203
|
+
return nil
|
204
|
+
end
|
167
205
|
end
|
168
206
|
|
169
207
|
|
@@ -200,8 +238,8 @@ end
|
|
200
238
|
# Mixin for creating records.
|
201
239
|
module RESTFramework::CreateModelMixin
|
202
240
|
def create
|
203
|
-
if self.get_recordset.respond_to?(:create!) &&
|
204
|
-
# Create with any properties inherited from the recordset
|
241
|
+
if self.get_recordset.respond_to?(:create!) && self.create_from_recordset
|
242
|
+
# Create with any properties inherited from the recordset.
|
205
243
|
@record = self.get_recordset.create!(self.get_create_params)
|
206
244
|
else
|
207
245
|
# 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,8 +14,8 @@ 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.
|
18
|
-
return @controller.request.query_parameters.select { |p|
|
17
|
+
fields = @controller.send(:get_filterset_fields)
|
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
|
21
21
|
end
|
@@ -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
|
40
|
-
|
39
|
+
return nil if @controller.class.ordering_query_param.blank?
|
40
|
+
ordering_fields = @controller.send(:get_ordering_fields)
|
41
41
|
order_string = @controller.params[@controller.class.ordering_query_param]
|
42
|
+
|
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
|
@@ -55,14 +63,31 @@ class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
|
|
55
63
|
# Order data according to the request query parameters.
|
56
64
|
def get_filtered_data(data)
|
57
65
|
ordering = self._get_ordering
|
66
|
+
reorder = !@controller.send(:ordering_no_reorder)
|
67
|
+
|
58
68
|
if ordering && !ordering.empty?
|
59
|
-
return data.order
|
69
|
+
return data.send(reorder ? :reorder : :order, _get_ordering)
|
60
70
|
end
|
71
|
+
|
61
72
|
return data
|
62
73
|
end
|
63
74
|
end
|
64
75
|
|
65
76
|
|
66
|
-
#
|
67
|
-
|
68
|
-
#
|
77
|
+
# Multi-field text searching on models.
|
78
|
+
class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
|
79
|
+
# Filter data according to the request query parameters.
|
80
|
+
def get_filtered_data(data)
|
81
|
+
fields = @controller.send(:get_search_fields)
|
82
|
+
search = @controller.request.query_parameters[@controller.send(:search_query_param)]
|
83
|
+
|
84
|
+
# Ensure we use array conditions to prevent SQL injection.
|
85
|
+
unless search.blank?
|
86
|
+
return data.where(fields.map { |f|
|
87
|
+
"CAST(#{f} AS CHAR) #{@controller.send(:search_ilike) ? "ILIKE" : "LIKE"} ?"
|
88
|
+
}.join(' OR '), *(["%#{search}%"] * fields.length))
|
89
|
+
end
|
90
|
+
|
91
|
+
return data
|
92
|
+
end
|
93
|
+
end
|
@@ -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.5
|
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-05-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -33,12 +33,11 @@ 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
|
-
- app/views/rest_framework/default.html.erb
|
40
40
|
- lib/rest_framework.rb
|
41
|
-
- lib/rest_framework/VERSION_STAMP
|
42
41
|
- lib/rest_framework/controller_mixins.rb
|
43
42
|
- lib/rest_framework/controller_mixins/base.rb
|
44
43
|
- lib/rest_framework/controller_mixins/models.rb
|
@@ -73,7 +72,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
73
72
|
- !ruby/object:Gem::Version
|
74
73
|
version: '0'
|
75
74
|
requirements: []
|
76
|
-
rubygems_version: 3.
|
75
|
+
rubygems_version: 3.1.4
|
77
76
|
signing_key:
|
78
77
|
specification_version: 4
|
79
78
|
summary: A framework for DRY RESTful APIs in Ruby on Rails.
|
File without changes
|
@@ -1 +0,0 @@
|
|
1
|
-
0.2.4
|