rest_framework 0.1.1 → 0.2.3
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 +3 -2
- data/lib/rest_framework.rb +2 -0
- data/lib/rest_framework/VERSION_STAMP +1 -1
- data/lib/rest_framework/controller_mixins/base.rb +41 -30
- data/lib/rest_framework/controller_mixins/models.rb +81 -71
- data/lib/rest_framework/errors.rb +26 -0
- data/lib/rest_framework/filters.rb +3 -3
- data/lib/rest_framework/generators.rb +5 -0
- data/lib/rest_framework/generators/controller_generator.rb +67 -0
- data/lib/rest_framework/routers.rb +6 -0
- data/lib/rest_framework/serializers.rb +76 -25
- data/lib/rest_framework/version.rb +11 -9
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '09c24135bd79001e0113aa92f21e63c5dcb67e93891db2a5df36b4d83d1c5da2'
|
|
4
|
+
data.tar.gz: ba3bd5829fdb9fd3c61a6050f5800c91a0077c76e78aedb049f850e581027f67
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c89c8af64c2eeb36dba79747770df5497d8505f2a4ced03ed6e25e81804a3a8a8f656e87bb0739a31d26be8e1f0fdcdd9e3df25721766e2e5d4bb2a01d40746f
|
|
7
|
+
data.tar.gz: 4bd5bdf9f2735622a07795245f605137c0310d16b3596fc544e9ade2046744a499ea0fdf1e7f11c7f5fc49f137650cceefadcf805884e6a6df5361ed919211d4
|
data/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/rest_framework)
|
|
4
4
|
[](https://travis-ci.org/gregschmit/rails-rest-framework)
|
|
5
|
+
[](https://coveralls.io/github/gregschmit/rails-rest-framework?branch=master)
|
|
5
6
|
|
|
6
7
|
A framework for DRY RESTful APIs in Ruby on Rails.
|
|
7
8
|
|
|
@@ -78,8 +79,8 @@ class Api::ReadOnlyMoviesController < ApiController
|
|
|
78
79
|
end
|
|
79
80
|
```
|
|
80
81
|
|
|
81
|
-
Note that you can also override
|
|
82
|
-
|
|
82
|
+
Note that you can also override the `get_recordset` instance method to override the API behavior
|
|
83
|
+
dynamically per-request.
|
|
83
84
|
|
|
84
85
|
### Routing
|
|
85
86
|
|
data/lib/rest_framework.rb
CHANGED
|
@@ -4,8 +4,10 @@ end
|
|
|
4
4
|
|
|
5
5
|
require_relative "rest_framework/controller_mixins"
|
|
6
6
|
require_relative "rest_framework/engine"
|
|
7
|
+
require_relative "rest_framework/errors"
|
|
7
8
|
require_relative "rest_framework/filters"
|
|
8
9
|
require_relative "rest_framework/paginators"
|
|
9
10
|
require_relative "rest_framework/routers"
|
|
10
11
|
require_relative "rest_framework/serializers"
|
|
11
12
|
require_relative "rest_framework/version"
|
|
13
|
+
require_relative "rest_framework/generators"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.2.3
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require_relative '../errors'
|
|
1
2
|
require_relative '../serializers'
|
|
2
3
|
|
|
3
4
|
|
|
@@ -33,6 +34,9 @@ module RESTFramework::BaseControllerMixin
|
|
|
33
34
|
|
|
34
35
|
# Add class attributes (with defaults) unless they already exist.
|
|
35
36
|
{
|
|
37
|
+
filter_pk_from_request_body: true,
|
|
38
|
+
exclude_body_fields: [:created_at, :created_by, :updated_at, :updated_by],
|
|
39
|
+
accept_generic_params_as_body_params: true,
|
|
36
40
|
extra_actions: nil,
|
|
37
41
|
extra_member_actions: nil,
|
|
38
42
|
filter_backends: nil,
|
|
@@ -42,14 +46,16 @@ module RESTFramework::BaseControllerMixin
|
|
|
42
46
|
page_size_query_param: 'page_size',
|
|
43
47
|
max_page_size: nil,
|
|
44
48
|
serializer_class: nil,
|
|
49
|
+
serialize_to_json: true,
|
|
50
|
+
serialize_to_xml: true,
|
|
45
51
|
singleton_controller: nil,
|
|
46
52
|
skip_actions: nil,
|
|
47
53
|
}.each do |a, default|
|
|
48
54
|
unless base.respond_to?(a)
|
|
49
55
|
base.class_attribute(a)
|
|
50
56
|
|
|
51
|
-
# Set default manually so we can still support Rails 4. Maybe later we can use the
|
|
52
|
-
#
|
|
57
|
+
# Set default manually so we can still support Rails 4. Maybe later we can use the default
|
|
58
|
+
# parameter on `class_attribute`.
|
|
53
59
|
base.send(:"#{a}=", default)
|
|
54
60
|
end
|
|
55
61
|
end
|
|
@@ -60,10 +66,10 @@ module RESTFramework::BaseControllerMixin
|
|
|
60
66
|
base.alias_method(:extra_collection_actions=, :extra_actions=)
|
|
61
67
|
end
|
|
62
68
|
|
|
63
|
-
#
|
|
69
|
+
# Skip csrf since this is an API.
|
|
64
70
|
base.skip_before_action(:verify_authenticity_token) rescue nil
|
|
65
71
|
|
|
66
|
-
#
|
|
72
|
+
# Handle some common exceptions.
|
|
67
73
|
base.rescue_from(ActiveRecord::RecordNotFound, with: :record_not_found)
|
|
68
74
|
base.rescue_from(ActiveRecord::RecordInvalid, with: :record_invalid)
|
|
69
75
|
base.rescue_from(ActiveRecord::RecordNotSaved, with: :record_not_saved)
|
|
@@ -118,7 +124,9 @@ module RESTFramework::BaseControllerMixin
|
|
|
118
124
|
begin
|
|
119
125
|
formatter = ActionDispatch::Routing::ConsoleFormatter::Sheet
|
|
120
126
|
rescue NameError
|
|
127
|
+
# :nocov:
|
|
121
128
|
formatter = ActionDispatch::Routing::ConsoleFormatter
|
|
129
|
+
# :nocov:
|
|
122
130
|
end
|
|
123
131
|
return ActionDispatch::Routing::RoutesInspector.new(Rails.application.routes.routes).format(
|
|
124
132
|
formatter.new
|
|
@@ -129,39 +137,42 @@ module RESTFramework::BaseControllerMixin
|
|
|
129
137
|
|
|
130
138
|
# Helper to render a browsable API for `html` format, along with basic `json`/`xml` formats, and
|
|
131
139
|
# with support or passing custom `kwargs` to the underlying `render` calls.
|
|
132
|
-
def api_response(payload, html_kwargs: nil,
|
|
140
|
+
def api_response(payload, html_kwargs: nil, **kwargs)
|
|
133
141
|
html_kwargs ||= {}
|
|
134
|
-
json_kwargs
|
|
135
|
-
xml_kwargs
|
|
136
|
-
|
|
137
|
-
#
|
|
138
|
-
|
|
142
|
+
json_kwargs = kwargs.delete(:json_kwargs) || {}
|
|
143
|
+
xml_kwargs = kwargs.delete(:xml_kwargs) || {}
|
|
144
|
+
|
|
145
|
+
# Raise helpful error if payload is nil. Usually this happens when a record is not found (e.g.,
|
|
146
|
+
# when passing something like `User.find_by(id: some_id)` to `api_response`). The caller should
|
|
147
|
+
# actually be calling `find_by!` to raise ActiveRecord::RecordNotFound and allowing the REST
|
|
148
|
+
# framework to catch this error and return an appropriate error response.
|
|
149
|
+
if payload.nil?
|
|
150
|
+
raise RESTFramework::NilPassedToAPIResponseError
|
|
151
|
+
end
|
|
139
152
|
|
|
140
153
|
respond_to do |format|
|
|
141
|
-
if
|
|
142
|
-
format.json {head :no_content}
|
|
143
|
-
format.xml {head :no_content}
|
|
154
|
+
if payload == ''
|
|
155
|
+
format.json {head :no_content} if self.serialize_to_json
|
|
156
|
+
format.xml {head :no_content} if self.serialize_to_xml
|
|
144
157
|
else
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
render(xml: payload, layout: false, **xkwargs)
|
|
155
|
-
}
|
|
156
|
-
end
|
|
158
|
+
format.json {
|
|
159
|
+
jkwargs = kwargs.merge(json_kwargs)
|
|
160
|
+
render(json: payload, layout: false, **jkwargs)
|
|
161
|
+
} if self.serialize_to_json
|
|
162
|
+
format.xml {
|
|
163
|
+
xkwargs = kwargs.merge(xml_kwargs)
|
|
164
|
+
render(xml: payload, layout: false, **xkwargs)
|
|
165
|
+
} if self.serialize_to_xml
|
|
166
|
+
# TODO: possibly support more formats here if supported?
|
|
157
167
|
end
|
|
158
168
|
format.html {
|
|
159
169
|
@payload = payload
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
@
|
|
170
|
+
if payload == ''
|
|
171
|
+
@json_payload = '' if self.serialize_to_json
|
|
172
|
+
@xml_payload = '' if self.serialize_to_xml
|
|
173
|
+
else
|
|
174
|
+
@json_payload = payload.to_json if self.serialize_to_json
|
|
175
|
+
@xml_payload = payload.to_xml if self.serialize_to_xml
|
|
165
176
|
end
|
|
166
177
|
@template_logo_text ||= "Rails REST Framework"
|
|
167
178
|
@title ||= self.controller_name.camelize
|
|
@@ -16,17 +16,18 @@ module RESTFramework::BaseModelControllerMixin
|
|
|
16
16
|
model: nil,
|
|
17
17
|
recordset: nil,
|
|
18
18
|
|
|
19
|
-
# Attributes for create/update parameters.
|
|
20
|
-
allowed_parameters: nil,
|
|
21
|
-
allowed_action_parameters: nil,
|
|
22
|
-
|
|
23
19
|
# Attributes for configuring record fields.
|
|
24
20
|
fields: nil,
|
|
25
21
|
action_fields: nil,
|
|
26
22
|
|
|
23
|
+
# Attributes for create/update parameters.
|
|
24
|
+
allowed_parameters: nil,
|
|
25
|
+
allowed_action_parameters: nil,
|
|
26
|
+
|
|
27
27
|
# Attributes for the default native serializer.
|
|
28
28
|
native_serializer_config: nil,
|
|
29
|
-
|
|
29
|
+
native_serializer_singular_config: nil,
|
|
30
|
+
native_serializer_plural_config: nil,
|
|
30
31
|
|
|
31
32
|
# Attributes for default model filtering (and ordering).
|
|
32
33
|
filterset_fields: nil,
|
|
@@ -34,13 +35,13 @@ module RESTFramework::BaseModelControllerMixin
|
|
|
34
35
|
ordering_query_param: 'ordering',
|
|
35
36
|
|
|
36
37
|
# Other misc attributes.
|
|
37
|
-
disable_creation_from_recordset:
|
|
38
|
+
disable_creation_from_recordset: false, # Option to disable `recordset.create` behavior.
|
|
38
39
|
}.each do |a, default|
|
|
39
40
|
unless base.respond_to?(a)
|
|
40
41
|
base.class_attribute(a)
|
|
41
42
|
|
|
42
|
-
# Set default manually so we can still support Rails 4. Maybe later we can use the
|
|
43
|
-
#
|
|
43
|
+
# Set default manually so we can still support Rails 4. Maybe later we can use the default
|
|
44
|
+
# parameter on `class_attribute`.
|
|
44
45
|
base.send(:"#{a}=", default)
|
|
45
46
|
end
|
|
46
47
|
end
|
|
@@ -49,36 +50,34 @@ module RESTFramework::BaseModelControllerMixin
|
|
|
49
50
|
|
|
50
51
|
protected
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
action_serializer_config = self.class.native_serializer_action_config || {}
|
|
56
|
-
action = self.action_name.to_sym
|
|
57
|
-
|
|
58
|
-
# Handle case where :index action is not defined.
|
|
59
|
-
if action == :index && !action_serializer_config.key?(:index)
|
|
60
|
-
# Default is :show if `singleton_controller`, otherwise :list.
|
|
61
|
-
action = self.class.singleton_controller ? :show : :list
|
|
62
|
-
end
|
|
53
|
+
def _get_specific_action_config(action_config_key, generic_config_key)
|
|
54
|
+
action_config = self.class.send(action_config_key) || {}
|
|
55
|
+
action = self.action_name&.to_sym
|
|
63
56
|
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
# Index action should use :list serializer if :index is not provided.
|
|
58
|
+
action = :list if action == :index && !action_config.key?(:index)
|
|
66
59
|
|
|
67
|
-
|
|
68
|
-
# @return [RESTFramework::BaseSerializer]
|
|
69
|
-
def get_serializer_class
|
|
70
|
-
return self.class.serializer_class || RESTFramework::NativeModelSerializer
|
|
60
|
+
return (action_config[action] if action) || self.class.send(generic_config_key)
|
|
71
61
|
end
|
|
72
62
|
|
|
73
63
|
# Get a list of parameters allowed for the current action.
|
|
74
64
|
def get_allowed_parameters
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
return _get_specific_action_config(:allowed_action_parameters, :allowed_parameters)&.map(&:to_s)
|
|
66
|
+
end
|
|
77
67
|
|
|
78
|
-
|
|
79
|
-
|
|
68
|
+
# Get a list of fields for the current action.
|
|
69
|
+
def get_fields
|
|
70
|
+
return (
|
|
71
|
+
_get_specific_action_config(:action_fields, :fields)&.map(&:to_s) ||
|
|
72
|
+
self.get_model&.column_names ||
|
|
73
|
+
[]
|
|
74
|
+
)
|
|
75
|
+
end
|
|
80
76
|
|
|
81
|
-
|
|
77
|
+
# Helper to get the configured serializer class, or `NativeSerializer` as a default.
|
|
78
|
+
# @return [RESTFramework::BaseSerializer]
|
|
79
|
+
def get_serializer_class
|
|
80
|
+
return self.class.serializer_class || RESTFramework::NativeSerializer
|
|
82
81
|
end
|
|
83
82
|
|
|
84
83
|
# Get the list of filtering backends to use.
|
|
@@ -89,70 +88,81 @@ module RESTFramework::BaseModelControllerMixin
|
|
|
89
88
|
]
|
|
90
89
|
end
|
|
91
90
|
|
|
92
|
-
#
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
# Filter the request body for keys in current action's allowed_parameters/fields config.
|
|
92
|
+
def get_body_params
|
|
93
|
+
return @_get_body_params ||= begin
|
|
94
|
+
fields = self.get_allowed_parameters || self.get_fields
|
|
95
|
+
|
|
96
|
+
# Filter the request body.
|
|
97
|
+
body_params = request.request_parameters.select { |p|
|
|
98
|
+
fields.include?(p)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# Add query params in place of missing body params, if configured.
|
|
102
|
+
if self.class.accept_generic_params_as_body_params
|
|
103
|
+
(fields - body_params.keys).each do |k|
|
|
104
|
+
if (value = params[k])
|
|
105
|
+
body_params[k] = value
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
96
109
|
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
# Filter primary key if configured.
|
|
111
|
+
if self.class.filter_pk_from_request_body
|
|
112
|
+
body_params.delete(self.get_model&.primary_key)
|
|
113
|
+
end
|
|
99
114
|
|
|
100
|
-
|
|
101
|
-
|
|
115
|
+
# Filter fields in exclude_body_fields.
|
|
116
|
+
(self.class.exclude_body_fields || []).each do |f|
|
|
117
|
+
body_params.delete(f.to_s)
|
|
118
|
+
end
|
|
102
119
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
fields = self.get_allowed_parameters || self.get_fields
|
|
106
|
-
return @get_body_params ||= (request.request_parameters.select { |p|
|
|
107
|
-
fields.include?(p.to_sym) || fields.include?(p.to_s)
|
|
108
|
-
})
|
|
120
|
+
body_params
|
|
121
|
+
end
|
|
109
122
|
end
|
|
110
123
|
alias :get_create_params :get_body_params
|
|
111
124
|
alias :get_update_params :get_body_params
|
|
112
125
|
|
|
113
|
-
# Get a record by the primary key from the filtered recordset.
|
|
126
|
+
# Get a record by the primary key from the (non-filtered) recordset.
|
|
114
127
|
def get_record
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return records.find(pk)
|
|
128
|
+
if pk = params[self.get_model.primary_key]
|
|
129
|
+
return self.get_recordset.find(pk)
|
|
118
130
|
end
|
|
119
131
|
return nil
|
|
120
132
|
end
|
|
121
133
|
|
|
122
|
-
#
|
|
123
|
-
def
|
|
134
|
+
# Get the model for this controller.
|
|
135
|
+
def get_model(from_get_recordset: false)
|
|
124
136
|
return @model if instance_variable_defined?(:@model) && @model
|
|
125
|
-
return self.class.model if self.class.model
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
return (@model = self.class.model) if self.class.model
|
|
138
|
+
|
|
139
|
+
# Delegate to the recordset's model, if it's defined.
|
|
140
|
+
unless from_get_recordset # prevent infinite recursion
|
|
141
|
+
if (recordset = self.get_recordset)
|
|
142
|
+
return @model = recordset.klass
|
|
143
|
+
end
|
|
129
144
|
end
|
|
145
|
+
|
|
146
|
+
# Try to determine model from controller name.
|
|
130
147
|
begin
|
|
131
148
|
return (@model = self.class.name.demodulize.match(/(.*)Controller/)[1].singularize.constantize)
|
|
132
149
|
rescue NameError
|
|
133
150
|
end
|
|
134
|
-
return nil
|
|
135
|
-
end
|
|
136
151
|
|
|
137
|
-
# Internal interface for get_recordset, protecting against infinite recursion with get_model.
|
|
138
|
-
def _get_recordset(from_internal_get_model: false)
|
|
139
|
-
return @recordset if instance_variable_defined?(:@recordset) && @recordset
|
|
140
|
-
return self.class.recordset if self.class.recordset
|
|
141
|
-
unless from_internal_get_model # prevent infinite recursion
|
|
142
|
-
model = self._get_model(from_internal_get_recordset: true)
|
|
143
|
-
return (@recordset = model.all) if model
|
|
144
|
-
end
|
|
145
152
|
return nil
|
|
146
153
|
end
|
|
147
154
|
|
|
148
|
-
# Get the model for this controller.
|
|
149
|
-
def get_model
|
|
150
|
-
return _get_model
|
|
151
|
-
end
|
|
152
|
-
|
|
153
155
|
# Get the set of records this controller has access to.
|
|
154
156
|
def get_recordset
|
|
155
|
-
return
|
|
157
|
+
return @recordset if instance_variable_defined?(:@recordset) && @recordset
|
|
158
|
+
return (@recordset = self.class.recordset) if self.class.recordset
|
|
159
|
+
|
|
160
|
+
# If there is a model, return that model's default scope (all records by default).
|
|
161
|
+
if (model = self.get_model(from_get_recordset: true))
|
|
162
|
+
return @recordset = model.all
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
return nil
|
|
156
166
|
end
|
|
157
167
|
end
|
|
158
168
|
|
|
@@ -219,7 +229,7 @@ module RESTFramework::DestroyModelMixin
|
|
|
219
229
|
def destroy
|
|
220
230
|
@record = self.get_record
|
|
221
231
|
@record.destroy!
|
|
222
|
-
api_response(
|
|
232
|
+
api_response('')
|
|
223
233
|
end
|
|
224
234
|
end
|
|
225
235
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Top-level class for all REST Framework errors.
|
|
2
|
+
class RESTFramework::Error < StandardError
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
class RESTFramework::NilPassedToAPIResponseError < RESTFramework::Error
|
|
6
|
+
def message
|
|
7
|
+
return <<~MSG.split("\n").join(' ')
|
|
8
|
+
Payload of `nil` was passed to `api_response`; this is unsupported. If you want a blank
|
|
9
|
+
response, pass `''` (an empty string) as the payload. If this was the result of a `find_by`
|
|
10
|
+
(or similar Active Record method) not finding a record, you should use the bang version (e.g.,
|
|
11
|
+
`find_by!`) to raise `ActiveRecord::RecordNotFound`, which the REST controller will catch and
|
|
12
|
+
return an appropriate error response.
|
|
13
|
+
MSG
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class RESTFramework::UnserializableError < RESTFramework::Error
|
|
18
|
+
def initialize(object)
|
|
19
|
+
@object = object
|
|
20
|
+
return super
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def message
|
|
24
|
+
return "Unable to serialize `#{@object.inspect}` (of type `#{@object.class}`)."
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -14,10 +14,10 @@ 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 || @controller.send(:get_fields)
|
|
17
|
+
fields = @controller.class.filterset_fields&.map(&:to_s) || @controller.send(:get_fields)
|
|
18
18
|
return @controller.request.query_parameters.select { |p|
|
|
19
|
-
fields.include?(p
|
|
20
|
-
}.
|
|
19
|
+
fields.include?(p)
|
|
20
|
+
}.to_h.symbolize_keys # convert from HashWithIndifferentAccess to Hash w/keys
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# Filter data according to the request query parameters.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require 'rails/generators'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Some projects don't have the inflection "REST" as an acronym, so this is a helper class to prevent
|
|
5
|
+
# this generator from being namespaced under `r_e_s_t_framework`.
|
|
6
|
+
# :nocov:
|
|
7
|
+
class RESTFrameworkCustomGeneratorControllerNamespace < String
|
|
8
|
+
def camelize
|
|
9
|
+
return "RESTFramework"
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
# :nocov:
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RESTFramework::Generators::ControllerGenerator < Rails::Generators::Base
|
|
16
|
+
PATH_REGEX = /^\/*([a-z0-9_\/]*[a-z0-9_])(?:[\.a-z\/]*)$/
|
|
17
|
+
|
|
18
|
+
desc <<~END
|
|
19
|
+
Description:
|
|
20
|
+
Generates a new REST Framework controller.
|
|
21
|
+
|
|
22
|
+
Specify the controller as a path, including the module, if needed, like:
|
|
23
|
+
'parent_module/controller_name'.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
`rails generate rest_framework:controller user_api/groups`
|
|
27
|
+
|
|
28
|
+
Generates a controller at `app/controllers/user_api/groups_controller.rb` named
|
|
29
|
+
`UserApi::GroupsController`.
|
|
30
|
+
END
|
|
31
|
+
|
|
32
|
+
argument :path, type: :string
|
|
33
|
+
class_option(
|
|
34
|
+
:parent_class,
|
|
35
|
+
type: :string,
|
|
36
|
+
default: 'ApplicationController',
|
|
37
|
+
desc: "Inheritance parent",
|
|
38
|
+
)
|
|
39
|
+
class_option(
|
|
40
|
+
:include_base,
|
|
41
|
+
type: :boolean,
|
|
42
|
+
default: false,
|
|
43
|
+
desc: "Include `BaseControllerMixin`, not `ModelControllerMixin`",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Some projects may not have the inflection "REST" as an acronym, which changes this generator to
|
|
47
|
+
# be namespaced in `r_e_s_t_framework`, which is weird.
|
|
48
|
+
def self.namespace
|
|
49
|
+
return RESTFrameworkCustomGeneratorControllerNamespace.new("rest_framework:controller")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def create_rest_controller_file
|
|
53
|
+
unless (path_match = PATH_REGEX.match(self.path))
|
|
54
|
+
raise StandardError.new("Path isn't correct.")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
cleaned_path = path_match[1]
|
|
58
|
+
content = <<~END
|
|
59
|
+
class #{cleaned_path.camelize}Controller < #{options[:parent_class]}
|
|
60
|
+
include RESTFramework::#{
|
|
61
|
+
options[:include_base] ? "BaseControllerMixin" : "ModelControllerMixin"
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
END
|
|
65
|
+
create_file("app/controllers/#{path}_controller.rb", content)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'action_dispatch/routing/mapper'
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
module ActionDispatch::Routing
|
|
4
5
|
class Mapper
|
|
5
6
|
# Internal helper to take extra_actions hash and convert to a consistent format.
|
|
@@ -24,6 +25,11 @@ module ActionDispatch::Routing
|
|
|
24
25
|
path = v.delete(:path)
|
|
25
26
|
end
|
|
26
27
|
|
|
28
|
+
# Set the action to be the action key unless it's already defined.
|
|
29
|
+
if !kwargs[:action]
|
|
30
|
+
kwargs[:action] = k
|
|
31
|
+
end
|
|
32
|
+
|
|
27
33
|
# Pass any further kwargs to the underlying Rails interface.
|
|
28
34
|
kwargs = kwargs.merge(v)
|
|
29
35
|
elsif v.is_a?(Symbol) || v.is_a?(String)
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
class RESTFramework::BaseSerializer
|
|
2
|
-
attr_reader :errors
|
|
3
|
-
|
|
4
2
|
def initialize(object: nil, controller: nil, **kwargs)
|
|
5
3
|
@object = object
|
|
6
4
|
@controller = controller
|
|
7
5
|
end
|
|
6
|
+
|
|
7
|
+
def serialize
|
|
8
|
+
raise NotImplementedError
|
|
9
|
+
end
|
|
8
10
|
end
|
|
9
11
|
|
|
10
12
|
|
|
11
|
-
# This serializer uses `.
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
class RESTFramework::NativeModelSerializer < RESTFramework::BaseSerializer
|
|
13
|
+
# This serializer uses `.serializable_hash` to convert objects to Ruby primitives (with the
|
|
14
|
+
# top-level being either an array or a hash).
|
|
15
|
+
class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
|
|
15
16
|
class_attribute :config
|
|
16
17
|
class_attribute :singular_config
|
|
17
18
|
class_attribute :plural_config
|
|
@@ -21,12 +22,19 @@ class RESTFramework::NativeModelSerializer < RESTFramework::BaseSerializer
|
|
|
21
22
|
super(**kwargs)
|
|
22
23
|
|
|
23
24
|
if many.nil?
|
|
24
|
-
|
|
25
|
+
# Determine if we are dealing with many objects or just one.
|
|
26
|
+
@many = @object.is_a?(Enumerable)
|
|
25
27
|
else
|
|
26
28
|
@many = many
|
|
27
29
|
end
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
# Determine model either explicitly, or by inspecting @object or @controller.
|
|
32
|
+
@model = model
|
|
33
|
+
@model ||= @object.class if @object.is_a?(ActiveRecord::Base)
|
|
34
|
+
@model ||= @object[0].class if (
|
|
35
|
+
@many && @object.is_a?(Enumerable) && @object.is_a?(ActiveRecord::Base)
|
|
36
|
+
)
|
|
37
|
+
@model ||= @controller.send(:get_model) if @controller
|
|
30
38
|
end
|
|
31
39
|
|
|
32
40
|
# Get controller action, if possible.
|
|
@@ -34,8 +42,8 @@ class RESTFramework::NativeModelSerializer < RESTFramework::BaseSerializer
|
|
|
34
42
|
return @controller&.action_name&.to_sym
|
|
35
43
|
end
|
|
36
44
|
|
|
37
|
-
# Get a locally defined configuration, if one is defined.
|
|
38
|
-
def
|
|
45
|
+
# Get a locally defined native serializer configuration, if one is defined.
|
|
46
|
+
def get_local_native_serializer_config
|
|
39
47
|
action = self.get_action
|
|
40
48
|
|
|
41
49
|
if action && self.action_config
|
|
@@ -45,43 +53,70 @@ class RESTFramework::NativeModelSerializer < RESTFramework::BaseSerializer
|
|
|
45
53
|
return self.action_config[action] if self.action_config[action]
|
|
46
54
|
end
|
|
47
55
|
|
|
48
|
-
# No action_config, so try singular/plural config.
|
|
49
|
-
return self.plural_config if @many && self.plural_config
|
|
50
|
-
return self.singular_config if
|
|
56
|
+
# No action_config, so try singular/plural config if explicitly instructed to via @many.
|
|
57
|
+
return self.plural_config if @many == true && self.plural_config
|
|
58
|
+
return self.singular_config if @many == false && self.singular_config
|
|
51
59
|
|
|
52
|
-
# Lastly, try returning the default config.
|
|
53
|
-
return self.config
|
|
60
|
+
# Lastly, try returning the default config, or singular/plural config in that order.
|
|
61
|
+
return self.config || self.singular_config || self.plural_config
|
|
54
62
|
end
|
|
55
63
|
|
|
56
|
-
#
|
|
64
|
+
# Helper to get a native serializer configuration from the controller.
|
|
65
|
+
def get_controller_native_serializer_config
|
|
66
|
+
return nil unless @controller
|
|
67
|
+
|
|
68
|
+
if @many == true
|
|
69
|
+
controller_serializer = @controller.try(:native_serializer_plural_config)
|
|
70
|
+
elsif @many == false
|
|
71
|
+
controller_serializer = @controller.try(:native_serializer_singular_config)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
return controller_serializer || @controller.try(:native_serializer_config)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Get a configuration passable to `serializable_hash` for the object.
|
|
57
78
|
def get_serializer_config
|
|
58
79
|
# Return a locally defined serializer config if one is defined.
|
|
59
|
-
if local_config = self.
|
|
80
|
+
if local_config = self.get_local_native_serializer_config
|
|
60
81
|
return local_config
|
|
61
82
|
end
|
|
62
83
|
|
|
63
|
-
# Return a serializer config if one is defined.
|
|
64
|
-
if serializer_config =
|
|
84
|
+
# Return a serializer config if one is defined on the controller.
|
|
85
|
+
if serializer_config = get_controller_native_serializer_config
|
|
65
86
|
return serializer_config
|
|
66
87
|
end
|
|
67
88
|
|
|
68
89
|
# If the config wasn't determined, build a serializer config from model fields.
|
|
69
|
-
fields = @controller.
|
|
70
|
-
|
|
71
|
-
|
|
90
|
+
fields = @controller.send(:get_fields) if @controller
|
|
91
|
+
if fields
|
|
92
|
+
if @model
|
|
93
|
+
columns, methods = fields.partition { |f| f.in?(@model.column_names) }
|
|
94
|
+
else
|
|
95
|
+
columns = fields
|
|
96
|
+
methods = []
|
|
97
|
+
end
|
|
98
|
+
|
|
72
99
|
return {only: columns, methods: methods}
|
|
73
100
|
end
|
|
74
101
|
|
|
102
|
+
# By default, pass an empty configuration, allowing the serialization of all columns.
|
|
75
103
|
return {}
|
|
76
104
|
end
|
|
77
105
|
|
|
78
106
|
# Convert the object (record or recordset) to Ruby primitives.
|
|
79
107
|
def serialize
|
|
80
108
|
if @object
|
|
81
|
-
|
|
82
|
-
|
|
109
|
+
begin
|
|
110
|
+
if @object.is_a?(Enumerable)
|
|
111
|
+
return @object.map { |r| r.serializable_hash(self.get_serializer_config) }
|
|
112
|
+
end
|
|
113
|
+
return @object.serializable_hash(self.get_serializer_config)
|
|
114
|
+
rescue NoMethodError
|
|
115
|
+
end
|
|
83
116
|
end
|
|
84
|
-
|
|
117
|
+
|
|
118
|
+
# Raise an error if we cannot serialize the object.
|
|
119
|
+
raise RESTFramework::UnserializableError.new(@object)
|
|
85
120
|
end
|
|
86
121
|
|
|
87
122
|
# Allow a serializer instance to be used as a hash directly in a nested serializer config.
|
|
@@ -112,3 +147,19 @@ class RESTFramework::NativeModelSerializer < RESTFramework::BaseSerializer
|
|
|
112
147
|
return @_nested_config[key] = value
|
|
113
148
|
end
|
|
114
149
|
end
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# :nocov:
|
|
153
|
+
# Alias NativeModelSerializer -> NativeSerializer.
|
|
154
|
+
class RESTFramework::NativeModelSerializer < RESTFramework::NativeSerializer
|
|
155
|
+
def initialize(**kwargs)
|
|
156
|
+
super
|
|
157
|
+
ActiveSupport::Deprecation.warn(
|
|
158
|
+
<<~MSG.split("\n").join(' ')
|
|
159
|
+
RESTFramework::NativeModelSerializer is deprecated and will be removed in future versions of
|
|
160
|
+
REST Framework; you should use RESTFramework::NativeSerializer instead.
|
|
161
|
+
MSG
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
# :nocov:
|
|
@@ -2,19 +2,21 @@ module RESTFramework
|
|
|
2
2
|
module Version
|
|
3
3
|
@_version = nil
|
|
4
4
|
|
|
5
|
-
def self.get_version
|
|
5
|
+
def self.get_version(skip_git: false)
|
|
6
6
|
# Return cached @_version, if available.
|
|
7
7
|
return @_version if @_version
|
|
8
8
|
|
|
9
9
|
# First, attempt to get the version from git.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
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
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
# Git failed, so try to find a VERSION_STAMP.
|
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.2.3
|
|
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-03-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -43,7 +43,10 @@ files:
|
|
|
43
43
|
- lib/rest_framework/controller_mixins/base.rb
|
|
44
44
|
- lib/rest_framework/controller_mixins/models.rb
|
|
45
45
|
- lib/rest_framework/engine.rb
|
|
46
|
+
- lib/rest_framework/errors.rb
|
|
46
47
|
- lib/rest_framework/filters.rb
|
|
48
|
+
- lib/rest_framework/generators.rb
|
|
49
|
+
- lib/rest_framework/generators/controller_generator.rb
|
|
47
50
|
- lib/rest_framework/paginators.rb
|
|
48
51
|
- lib/rest_framework/routers.rb
|
|
49
52
|
- lib/rest_framework/serializers.rb
|