rest_framework 0.5.3 → 0.5.4
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 +4 -4
- data/README.md +4 -4
- data/VERSION +1 -1
- data/app/views/rest_framework/_route.html.erb +8 -6
- data/app/views/rest_framework/_routes.html.erb +1 -1
- data/lib/rest_framework/controller_mixins/base.rb +0 -2
- data/lib/rest_framework/controller_mixins/models.rb +1 -2
- data/lib/rest_framework/filters.rb +3 -3
- data/lib/rest_framework/routers.rb +3 -3
- data/lib/rest_framework/serializers.rb +81 -35
- data/lib/rest_framework/utils.rb +50 -16
- data/lib/rest_framework/version.rb +9 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 413ee55f2eecbb8a8cdd3b133852d83a3e3fdb7e43f19c8deae12adcb3d38977
|
4
|
+
data.tar.gz: 6582a4d8acd988ad3034af3191b75454fcf602c5f968772e18fa9d5535b71dea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19cb82822923617face6c76e0fd36cfa001f23e88393210990842563b0483891dab03638e11c65bf1182db6ebe0d164cf09bb3e35a7ea06b9769c729e16b85d5
|
7
|
+
data.tar.gz: fc853858e022b126d67f878c63f2e46e18a1f03d03e2aeda90f7c8996b6a461bd41d41d44fadb59d46256aa9c35b18efc1c5e9b7970f712dce010af0c9178e68
|
data/README.md
CHANGED
@@ -120,9 +120,9 @@ using RVM. Then run `bundle install` to install the appropriate gems.
|
|
120
120
|
To run the test suite:
|
121
121
|
|
122
122
|
```shell
|
123
|
-
$
|
123
|
+
$ rails test
|
124
124
|
```
|
125
125
|
|
126
|
-
|
127
|
-
run `
|
128
|
-
|
126
|
+
The top-level `bin/rails` proxies all Rails commands to the test project, so you can operate it via
|
127
|
+
the usual commands. Ensure you run `rails db:setup` before running `rails server` or
|
128
|
+
`rails console`.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.4
|
@@ -1,9 +1,11 @@
|
|
1
1
|
<tr>
|
2
|
-
<td
|
2
|
+
<td>
|
3
|
+
<% if route[:route].name && link_args = route[:show_link_args] %>
|
4
|
+
<%= link_to route[:relative_path], self.send("#{route[:route].name}_path", *link_args) %>
|
5
|
+
<% else %>
|
6
|
+
<%= route[:relative_path] %>
|
7
|
+
<% end %>
|
8
|
+
</td>
|
3
9
|
<td><%= route[:verb] %></td>
|
4
|
-
|
5
|
-
<td><%= route[:controller] %>#<%= route[:action] %></td>
|
6
|
-
<% else %>
|
7
|
-
<td><%= route[:route_app] %></td>
|
8
|
-
<% end %>
|
10
|
+
<td><%= route[:controller] %>#<%= route[:action] %></td>
|
9
11
|
</tr>
|
@@ -31,6 +31,7 @@ module RESTFramework::BaseModelControllerMixin
|
|
31
31
|
native_serializer_config: nil,
|
32
32
|
native_serializer_singular_config: nil,
|
33
33
|
native_serializer_plural_config: nil,
|
34
|
+
native_serializer_only_query_param: "only",
|
34
35
|
native_serializer_except_query_param: "except",
|
35
36
|
|
36
37
|
# Attributes for default model filtering (and ordering).
|
@@ -57,8 +58,6 @@ module RESTFramework::BaseModelControllerMixin
|
|
57
58
|
end
|
58
59
|
end
|
59
60
|
|
60
|
-
protected
|
61
|
-
|
62
61
|
def _get_specific_action_config(action_config_key, generic_config_key)
|
63
62
|
action_config = self.class.send(action_config_key) || {}
|
64
63
|
action = self.action_name&.to_sym
|
@@ -13,7 +13,7 @@ end
|
|
13
13
|
class RESTFramework::ModelFilter < RESTFramework::BaseFilter
|
14
14
|
# Filter params for keys allowed by the current action's filterset_fields/fields config.
|
15
15
|
def _get_filter_params
|
16
|
-
fields = @controller.
|
16
|
+
fields = @controller.get_filterset_fields
|
17
17
|
return @controller.request.query_parameters.select { |p, _|
|
18
18
|
fields.include?(p)
|
19
19
|
}.to_h.symbolize_keys # convert from HashWithIndifferentAccess to Hash w/keys
|
@@ -36,7 +36,7 @@ class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
|
|
36
36
|
def _get_ordering
|
37
37
|
return nil if @controller.class.ordering_query_param.blank?
|
38
38
|
|
39
|
-
ordering_fields = @controller.
|
39
|
+
ordering_fields = @controller.get_ordering_fields
|
40
40
|
order_string = @controller.params[@controller.class.ordering_query_param]
|
41
41
|
|
42
42
|
unless order_string.blank?
|
@@ -76,7 +76,7 @@ end
|
|
76
76
|
class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
|
77
77
|
# Filter data according to the request query parameters.
|
78
78
|
def get_filtered_data(data)
|
79
|
-
fields = @controller.
|
79
|
+
fields = @controller.get_search_fields
|
80
80
|
search = @controller.request.query_parameters[@controller.class.search_query_param]
|
81
81
|
|
82
82
|
# Ensure we use array conditions to prevent SQL injection.
|
@@ -4,7 +4,7 @@ require_relative "utils"
|
|
4
4
|
module ActionDispatch::Routing
|
5
5
|
class Mapper
|
6
6
|
# Internal interface to get the controller class from the name and current scope.
|
7
|
-
|
7
|
+
def _get_controller_class(name, pluralize: true, fallback_reverse_pluralization: true)
|
8
8
|
# Get class name.
|
9
9
|
name = name.to_s.camelize # camelize to leave plural names plural
|
10
10
|
name = name.pluralize if pluralize
|
@@ -38,7 +38,7 @@ module ActionDispatch::Routing
|
|
38
38
|
end
|
39
39
|
|
40
40
|
# Interal interface for routing extra actions.
|
41
|
-
|
41
|
+
def _route_extra_actions(actions, &block)
|
42
42
|
actions.each do |action_config|
|
43
43
|
action_config[:methods].each do |m|
|
44
44
|
public_send(m, action_config[:path], **action_config[:kwargs])
|
@@ -52,7 +52,7 @@ module ActionDispatch::Routing
|
|
52
52
|
# not otherwise defined by the controller
|
53
53
|
# @param name [Symbol] the resource name, from which path and controller are deduced by default
|
54
54
|
# @param skip_undefined [Boolean] whether we should skip routing undefined resourceful actions
|
55
|
-
|
55
|
+
def _rest_resources(default_singular, name, skip_undefined: true, **kwargs, &block)
|
56
56
|
controller = kwargs.delete(:controller) || name
|
57
57
|
if controller.is_a?(Class)
|
58
58
|
controller_class = controller
|
@@ -43,7 +43,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
|
|
43
43
|
@model ||= @object[0].class if
|
44
44
|
@many && @object.is_a?(Enumerable) && @object.is_a?(ActiveRecord::Base)
|
45
45
|
|
46
|
-
@model ||= @controller.
|
46
|
+
@model ||= @controller.get_model if @controller
|
47
47
|
end
|
48
48
|
|
49
49
|
# Get controller action, if possible.
|
@@ -84,57 +84,103 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
|
|
84
84
|
end
|
85
85
|
|
86
86
|
# Helper to filter (mutate) a single subconfig for specific keys.
|
87
|
-
def self.
|
88
|
-
return
|
89
|
-
|
90
|
-
if
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
87
|
+
def self.filter_subcfg(subcfg, except: nil, only: nil, additive: false)
|
88
|
+
return subcfg unless except || only
|
89
|
+
return subcfg unless subcfg || additive
|
90
|
+
raise "Cannot pass `only` and `additive` to filter_subcfg." if only && additive
|
91
|
+
|
92
|
+
if subcfg.is_a?(Array)
|
93
|
+
subcfg = subcfg.map(&:to_sym)
|
94
|
+
if except
|
95
|
+
if additive
|
96
|
+
# Only add fields which are not already included.
|
97
|
+
subcfg += except - subcfg
|
98
|
+
else
|
99
|
+
subcfg -= except
|
100
|
+
end
|
101
|
+
elsif only
|
102
|
+
# Ignore `additive` in an `only` context, since it could causing data leaking.
|
103
|
+
unless additive
|
104
|
+
subcfg.select! { |c| c.in?(only) }
|
105
|
+
end
|
97
106
|
end
|
98
|
-
elsif
|
107
|
+
elsif subcfg.is_a?(Hash)
|
99
108
|
# Additive doesn't make sense in a hash context since we wouldn't know the values.
|
100
109
|
unless additive
|
101
|
-
|
102
|
-
|
110
|
+
if except
|
111
|
+
subcfg.symbolize_keys!
|
112
|
+
subcfg.reject! { |k, _v| k.in?(except) }
|
113
|
+
elsif only
|
114
|
+
subcfg.symbolize_keys!
|
115
|
+
subcfg.select! { |k, _v| k.in?(only) }
|
116
|
+
end
|
103
117
|
end
|
104
|
-
elsif !
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
118
|
+
elsif !subcfg
|
119
|
+
if additive && except
|
120
|
+
subcfg = except
|
121
|
+
end
|
122
|
+
else # Subcfg is a single element (assume string/symbol).
|
123
|
+
subcfg = subcfg.to_sym
|
124
|
+
|
125
|
+
if except
|
126
|
+
if subcfg.in?(except)
|
127
|
+
subcfg = [] unless additive
|
128
|
+
elsif additive
|
129
|
+
subcfg = [subcfg, *except]
|
130
|
+
end
|
131
|
+
elsif only && !additive && !subcfg.in?(only) # Protect only/additive data-leaking.
|
132
|
+
subcfg = []
|
111
133
|
end
|
112
134
|
end
|
113
135
|
|
114
|
-
return
|
136
|
+
return subcfg
|
115
137
|
end
|
116
138
|
|
117
139
|
# Helper to filter out configuration properties based on the :except query parameter.
|
118
|
-
def filter_except(
|
119
|
-
return
|
140
|
+
def filter_except(cfg)
|
141
|
+
return cfg unless @controller
|
120
142
|
|
121
|
-
|
122
|
-
|
143
|
+
except_param = @controller.class.try(:native_serializer_except_query_param)
|
144
|
+
only_param = @controller.class.try(:native_serializer_only_query_param)
|
145
|
+
if except_param && except = @controller.request.query_parameters[except_param].presence
|
123
146
|
except = except.split(",").map(&:strip).map(&:to_sym)
|
124
147
|
|
125
148
|
unless except.empty?
|
126
|
-
# Duplicate the
|
127
|
-
|
149
|
+
# Duplicate the cfg to avoid mutating class state.
|
150
|
+
cfg = cfg.deep_dup
|
128
151
|
|
129
152
|
# Filter `only`, `except` (additive), `include`, and `methods`.
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
153
|
+
if cfg[:only]
|
154
|
+
cfg[:only] = self.class.filter_subcfg(cfg[:only], except: except)
|
155
|
+
else
|
156
|
+
cfg[:except] = self.class.filter_subcfg(cfg[:except], except: except, additive: true)
|
157
|
+
end
|
158
|
+
cfg[:include] = self.class.filter_subcfg(cfg[:include], except: except)
|
159
|
+
cfg[:methods] = self.class.filter_subcfg(cfg[:methods], except: except)
|
160
|
+
end
|
161
|
+
elsif only_param && only = @controller.request.query_parameters[only_param].presence
|
162
|
+
only = only.split(",").map(&:strip).map(&:to_sym)
|
163
|
+
|
164
|
+
unless only.empty?
|
165
|
+
# Duplicate the cfg to avoid mutating class state.
|
166
|
+
cfg = cfg.deep_dup
|
167
|
+
|
168
|
+
# For the `except` part of the serializer, we need to append any columns not in `only`.
|
169
|
+
model = @controller.get_model
|
170
|
+
except_cols = model&.column_names&.map(&:to_sym)&.reject { |c| c.in?(only) }
|
171
|
+
|
172
|
+
# Filter `only`, `except` (additive), `include`, and `methods`.
|
173
|
+
if cfg[:only]
|
174
|
+
cfg[:only] = self.class.filter_subcfg(cfg[:only], only: only)
|
175
|
+
else
|
176
|
+
cfg[:except] = self.class.filter_subcfg(cfg[:except], except: except_cols, additive: true)
|
177
|
+
end
|
178
|
+
cfg[:include] = self.class.filter_subcfg(cfg[:include], only: only)
|
179
|
+
cfg[:methods] = self.class.filter_subcfg(cfg[:methods], only: only)
|
134
180
|
end
|
135
181
|
end
|
136
182
|
|
137
|
-
return
|
183
|
+
return cfg
|
138
184
|
end
|
139
185
|
|
140
186
|
# Get the raw serializer config.
|
@@ -150,7 +196,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
|
|
150
196
|
end
|
151
197
|
|
152
198
|
# If the config wasn't determined, build a serializer config from model fields.
|
153
|
-
fields = @controller.
|
199
|
+
fields = @controller.get_fields if @controller
|
154
200
|
if fields
|
155
201
|
if @model
|
156
202
|
columns, methods = fields.partition { |f| f.in?(@model.column_names) }
|
@@ -168,7 +214,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
|
|
168
214
|
|
169
215
|
# Get a configuration passable to `serializable_hash` for the object, filtered if required.
|
170
216
|
def get_serializer_config
|
171
|
-
return filter_except(self._get_raw_serializer_config)
|
217
|
+
return @_serializer_config ||= filter_except(self._get_raw_serializer_config)
|
172
218
|
end
|
173
219
|
|
174
220
|
def serialize(**kwargs)
|
data/lib/rest_framework/utils.rb
CHANGED
@@ -37,34 +37,68 @@ module RESTFramework::Utils
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
# Helper to get the
|
41
|
-
def self.
|
42
|
-
application_routes.router.recognize(request)
|
43
|
-
|
44
|
-
|
40
|
+
# Helper to get the first route pattern which matches the given request.
|
41
|
+
def self.get_request_route(application_routes, request)
|
42
|
+
application_routes.router.recognize(request) { |route, _| return route }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Helper to get the route pattern for a route, stripped of the `(:format)` segment.
|
46
|
+
def self.get_route_pattern(route)
|
47
|
+
return route.path.spec.to_s.gsub(/\(\.:format\)$/, "")
|
45
48
|
end
|
46
49
|
|
47
|
-
# Helper
|
48
|
-
|
49
|
-
|
50
|
-
|
50
|
+
# Helper to normalize a path pattern by replacing URL params with generic placeholder, and
|
51
|
+
# removing the `(.:format)` at the end.
|
52
|
+
def self.normalize_path(path)
|
53
|
+
return path.gsub("(.:format)", "").gsub(/:[0-9A-Za-z_-]+/, ":x")
|
54
|
+
end
|
55
|
+
|
56
|
+
# Helper for showing routes under a controller action; used for the browsable API.
|
57
|
+
def self.get_routes(application_routes, request, current_route: nil)
|
58
|
+
current_route ||= self.get_request_route(application_routes, request)
|
59
|
+
current_path = current_route.path.spec.to_s
|
60
|
+
current_levels = current_path.count("/")
|
61
|
+
current_normalized_path = self.normalize_path(current_path)
|
51
62
|
|
52
|
-
# Return routes that match our current route subdomain/pattern, grouped by controller.
|
63
|
+
# Return routes that match our current route subdomain/pattern, grouped by controller. We
|
64
|
+
# precompute certain properties of the route for performance.
|
53
65
|
return application_routes.routes.map { |r|
|
54
66
|
path = r.path.spec.to_s
|
67
|
+
levels = path.count("/")
|
68
|
+
|
69
|
+
# Show link if the route is GET and our current route has all required URL params.
|
70
|
+
if r.verb == "GET" && r.path.required_names.length == current_route.path.required_names.length
|
71
|
+
show_link_args = current_route.path.required_names.map { |n| request.params[n] }.compact
|
72
|
+
else
|
73
|
+
show_link_args = nil
|
74
|
+
end
|
75
|
+
|
55
76
|
{
|
77
|
+
route: r,
|
56
78
|
verb: r.verb,
|
57
79
|
path: path,
|
58
|
-
|
80
|
+
normalized_path: self.normalize_path(path),
|
81
|
+
relative_path: path.split("/")[current_levels..]&.join("/"),
|
59
82
|
controller: r.defaults[:controller].presence,
|
83
|
+
action: r.defaults[:action].presence,
|
60
84
|
subdomain: r.defaults[:subdomain].presence,
|
61
|
-
|
62
|
-
|
85
|
+
levels: levels,
|
86
|
+
show_link_args: show_link_args,
|
63
87
|
}
|
64
88
|
}.select { |r|
|
65
|
-
|
89
|
+
(
|
90
|
+
r[:subdomain] == request.subdomain.presence &&
|
91
|
+
r[:normalized_path].start_with?(current_normalized_path) &&
|
92
|
+
r[:controller] &&
|
93
|
+
r[:action]
|
94
|
+
)
|
66
95
|
}.sort_by { |r|
|
67
|
-
|
68
|
-
|
96
|
+
# Sort by levels first, so the routes matching closely with current request show first, then
|
97
|
+
# by the path, and finally by the HTTP verb.
|
98
|
+
[r[:levels], r[:path], HTTP_METHOD_ORDERING.index(r[:verb]) || 99]
|
99
|
+
}.group_by { |r| r[:controller] }.sort_by { |c, _r|
|
100
|
+
# Sort the controller groups by current controller first, then depth, then alphanumerically.
|
101
|
+
[request.params[:controller] == c ? 0 : 1, c.count("/"), c]
|
102
|
+
}.to_h
|
69
103
|
end
|
70
104
|
end
|
@@ -6,19 +6,25 @@ module RESTFramework
|
|
6
6
|
def self.get_version(skip_git: false)
|
7
7
|
# First, attempt to get the version from git.
|
8
8
|
unless skip_git
|
9
|
-
version = `git describe --dirty
|
9
|
+
version = `git describe --dirty 2>/dev/null`&.strip
|
10
10
|
return version unless !version || version.empty?
|
11
11
|
end
|
12
12
|
|
13
13
|
# Git failed or was skipped, so try to find a VERSION file.
|
14
14
|
begin
|
15
15
|
version = File.read(VERSION_FILEPATH)&.strip
|
16
|
-
return version unless !version || version.
|
16
|
+
return version unless !version || version.empty?
|
17
17
|
rescue SystemCallError
|
18
18
|
end
|
19
19
|
|
20
|
+
# If that fails, then try to get a plain commit SHA from git.
|
21
|
+
unless skip_git
|
22
|
+
version = `git describe --dirty --always`&.strip
|
23
|
+
return "0.#{version}" unless !version || version.empty?
|
24
|
+
end
|
25
|
+
|
20
26
|
# No VERSION file, so version is unknown.
|
21
|
-
return "unknown"
|
27
|
+
return "0.unknown"
|
22
28
|
end
|
23
29
|
|
24
30
|
def self.stamp_version
|
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.5.
|
4
|
+
version: 0.5.4
|
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: 2022-03-
|
11
|
+
date: 2022-03-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|