rest_framework 0.6.13 → 0.7.1
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/VERSION +1 -1
- data/app/views/layouts/rest_framework.html.erb +3 -0
- data/lib/rest_framework/controller_mixins/base.rb +88 -36
- data/lib/rest_framework/controller_mixins/models.rb +98 -83
- data/lib/rest_framework/generators/controller_generator.rb +1 -1
- data/lib/rest_framework/routers.rb +7 -11
- data/lib/rest_framework/serializers.rb +1 -1
- data/lib/rest_framework/utils.rb +40 -11
- data/lib/rest_framework.rb +26 -1
- 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: e303d3d1aae81be9cdf85affab8095f8cfe3115ba3beb0a22a7c6b8c9c91f17d
|
|
4
|
+
data.tar.gz: '058ae280bbe91f87b88ad4284bea00cbb914e577c56280e3c0c0d7f0c1e55126'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a831e67a8b0328f6ee80262543ddea4be439ac47b05a7615304728bc6ed708b3563ab3bd4c278a95d57b2e8bfb614e31a8b196649c3f0d51e55c14a0c1b7ced
|
|
7
|
+
data.tar.gz: 25d0b5ef65981c58dee75c168243c9f19dd8ae1c4acb86fbfa84ba80ca3794e6b5e88a392892a988070980b9f1c7b906e61fef9249e5d2f2c74ce70f5a467872
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.7.1
|
|
@@ -57,6 +57,9 @@
|
|
|
57
57
|
<% end %>
|
|
58
58
|
<button type="button" class="btn btn-primary" onclick="rrfRefresh(this)">GET</button>
|
|
59
59
|
</div>
|
|
60
|
+
<% if @description.present? %>
|
|
61
|
+
<br><br><p style="display: inline-block; margin-bottom: 0"><%= @description %></p>
|
|
62
|
+
<% end %>
|
|
60
63
|
</div>
|
|
61
64
|
</div>
|
|
62
65
|
<hr/>
|
|
@@ -6,12 +6,59 @@ require_relative "../utils"
|
|
|
6
6
|
# the ability to route arbitrary actions with `extra_actions`. This is also where `api_response`
|
|
7
7
|
# is defined.
|
|
8
8
|
module RESTFramework::BaseControllerMixin
|
|
9
|
+
RRF_BASE_CONTROLLER_CONFIG = {
|
|
10
|
+
filter_pk_from_request_body: true,
|
|
11
|
+
exclude_body_fields: [:created_at, :created_by, :updated_at, :updated_by].freeze,
|
|
12
|
+
accept_generic_params_as_body_params: false,
|
|
13
|
+
show_backtrace: false,
|
|
14
|
+
extra_actions: nil,
|
|
15
|
+
extra_member_actions: nil,
|
|
16
|
+
filter_backends: nil,
|
|
17
|
+
singleton_controller: nil,
|
|
18
|
+
|
|
19
|
+
# Metadata and display options.
|
|
20
|
+
title: nil,
|
|
21
|
+
description: nil,
|
|
22
|
+
inflect_acronyms: ["ID", "REST", "API"].freeze,
|
|
23
|
+
|
|
24
|
+
# Options related to serialization.
|
|
25
|
+
rescue_unknown_format_with: :json,
|
|
26
|
+
serializer_class: nil,
|
|
27
|
+
serialize_to_json: true,
|
|
28
|
+
serialize_to_xml: true,
|
|
29
|
+
|
|
30
|
+
# Options related to pagination.
|
|
31
|
+
paginator_class: nil,
|
|
32
|
+
page_size: 20,
|
|
33
|
+
page_query_param: "page",
|
|
34
|
+
page_size_query_param: "page_size",
|
|
35
|
+
max_page_size: nil,
|
|
36
|
+
|
|
37
|
+
# Option to disable serializer adapters by default, mainly introduced because Active Model
|
|
38
|
+
# Serializers will do things like serialize `[]` into `{"":[]}`.
|
|
39
|
+
disable_adapters_by_default: true,
|
|
40
|
+
}
|
|
41
|
+
|
|
9
42
|
# Default action for API root.
|
|
10
43
|
def root
|
|
11
|
-
api_response({message: "This is the root
|
|
44
|
+
api_response({message: "This is the API root."})
|
|
12
45
|
end
|
|
13
46
|
|
|
14
47
|
module ClassMethods
|
|
48
|
+
# Get the title of this controller. By default, this is the name of the controller class,
|
|
49
|
+
# titleized and with any custom inflection acronyms applied.
|
|
50
|
+
def get_title
|
|
51
|
+
return self.title || RESTFramework::Utils.inflect(
|
|
52
|
+
self.name.demodulize.chomp("Controller").titleize(keep_id_suffix: true),
|
|
53
|
+
self.inflect_acronyms,
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Get a label from a field/column name, titleized and inflected.
|
|
58
|
+
def get_label(s)
|
|
59
|
+
return RESTFramework::Utils.inflect(s.titleize(keep_id_suffix: true), self.inflect_acronyms)
|
|
60
|
+
end
|
|
61
|
+
|
|
15
62
|
# Collect actions (including extra actions) metadata for this controller.
|
|
16
63
|
def get_actions_metadata
|
|
17
64
|
actions = {}
|
|
@@ -20,12 +67,14 @@ module RESTFramework::BaseControllerMixin
|
|
|
20
67
|
RESTFramework::BUILTIN_ACTIONS.merge(
|
|
21
68
|
RESTFramework::RRF_BUILTIN_ACTIONS,
|
|
22
69
|
).each do |action, methods|
|
|
23
|
-
|
|
70
|
+
if self.method_defined?(action)
|
|
71
|
+
actions[action] = {path: "", methods: methods, type: :builtin}
|
|
72
|
+
end
|
|
24
73
|
end
|
|
25
74
|
|
|
26
75
|
# Add extra actions.
|
|
27
76
|
if extra_actions = self.try(:extra_actions)
|
|
28
|
-
actions.merge!(RESTFramework::Utils.parse_extra_actions(extra_actions))
|
|
77
|
+
actions.merge!(RESTFramework::Utils.parse_extra_actions(extra_actions, controller: self))
|
|
29
78
|
end
|
|
30
79
|
|
|
31
80
|
return actions
|
|
@@ -37,12 +86,14 @@ module RESTFramework::BaseControllerMixin
|
|
|
37
86
|
|
|
38
87
|
# Start with builtin actions.
|
|
39
88
|
RESTFramework::BUILTIN_MEMBER_ACTIONS.each do |action, methods|
|
|
40
|
-
|
|
89
|
+
if self.method_defined?(action)
|
|
90
|
+
actions[action] = {path: "", methods: methods, type: :builtin}
|
|
91
|
+
end
|
|
41
92
|
end
|
|
42
93
|
|
|
43
94
|
# Add extra actions.
|
|
44
95
|
if extra_actions = self.try(:extra_member_actions)
|
|
45
|
-
actions.merge!(RESTFramework::Utils.parse_extra_actions(extra_actions))
|
|
96
|
+
actions.merge!(RESTFramework::Utils.parse_extra_actions(extra_actions, controller: self))
|
|
46
97
|
end
|
|
47
98
|
|
|
48
99
|
return actions
|
|
@@ -51,8 +102,8 @@ module RESTFramework::BaseControllerMixin
|
|
|
51
102
|
# Get a hash of metadata to be rendered in the `OPTIONS` response. Cache the result.
|
|
52
103
|
def get_options_metadata
|
|
53
104
|
return @_base_options_metadata ||= {
|
|
54
|
-
|
|
55
|
-
description: self.
|
|
105
|
+
title: self.get_title,
|
|
106
|
+
description: self.description,
|
|
56
107
|
renders: [
|
|
57
108
|
"text/html",
|
|
58
109
|
self.serialize_to_json ? "application/json" : nil,
|
|
@@ -62,6 +113,18 @@ module RESTFramework::BaseControllerMixin
|
|
|
62
113
|
member_actions: self.get_member_actions_metadata,
|
|
63
114
|
}.compact
|
|
64
115
|
end
|
|
116
|
+
|
|
117
|
+
# Define any behavior to execute at the end of controller definition.
|
|
118
|
+
# :nocov:
|
|
119
|
+
def rrf_finalize
|
|
120
|
+
if RESTFramework.config.freeze_config
|
|
121
|
+
self::RRF_BASE_CONTROLLER_CONFIG.keys.each { |k|
|
|
122
|
+
v = self.send(k)
|
|
123
|
+
v.freeze if v.is_a?(Hash) || v.is_a?(Array)
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
# :nocov:
|
|
65
128
|
end
|
|
66
129
|
|
|
67
130
|
def self.included(base)
|
|
@@ -70,34 +133,7 @@ module RESTFramework::BaseControllerMixin
|
|
|
70
133
|
base.extend(ClassMethods)
|
|
71
134
|
|
|
72
135
|
# Add class attributes (with defaults) unless they already exist.
|
|
73
|
-
|
|
74
|
-
filter_pk_from_request_body: true,
|
|
75
|
-
exclude_body_fields: [:created_at, :created_by, :updated_at, :updated_by],
|
|
76
|
-
accept_generic_params_as_body_params: false,
|
|
77
|
-
show_backtrace: false,
|
|
78
|
-
extra_actions: nil,
|
|
79
|
-
extra_member_actions: nil,
|
|
80
|
-
filter_backends: nil,
|
|
81
|
-
singleton_controller: nil,
|
|
82
|
-
metadata: nil,
|
|
83
|
-
|
|
84
|
-
# Options related to serialization.
|
|
85
|
-
rescue_unknown_format_with: :json,
|
|
86
|
-
serializer_class: nil,
|
|
87
|
-
serialize_to_json: true,
|
|
88
|
-
serialize_to_xml: true,
|
|
89
|
-
|
|
90
|
-
# Options related to pagination.
|
|
91
|
-
paginator_class: nil,
|
|
92
|
-
page_size: 20,
|
|
93
|
-
page_query_param: "page",
|
|
94
|
-
page_size_query_param: "page_size",
|
|
95
|
-
max_page_size: nil,
|
|
96
|
-
|
|
97
|
-
# Option to disable serializer adapters by default, mainly introduced because Active Model
|
|
98
|
-
# Serializers will do things like serialize `[]` into `{"":[]}`.
|
|
99
|
-
disable_adapters_by_default: true,
|
|
100
|
-
}.each do |a, default|
|
|
136
|
+
RRF_BASE_CONTROLLER_CONFIG.each do |a, default|
|
|
101
137
|
next if base.respond_to?(a)
|
|
102
138
|
|
|
103
139
|
base.class_attribute(a)
|
|
@@ -126,6 +162,21 @@ module RESTFramework::BaseControllerMixin
|
|
|
126
162
|
base.rescue_from(ActiveRecord::RecordNotSaved, with: :record_not_saved)
|
|
127
163
|
base.rescue_from(ActiveRecord::RecordNotDestroyed, with: :record_not_destroyed)
|
|
128
164
|
base.rescue_from(ActiveModel::UnknownAttributeError, with: :unknown_attribute_error)
|
|
165
|
+
|
|
166
|
+
# Use `TracePoint` hook to automatically call `rrf_finalize`.
|
|
167
|
+
unless RESTFramework.config.disable_auto_finalize
|
|
168
|
+
# :nocov:
|
|
169
|
+
TracePoint.trace(:end) do |t|
|
|
170
|
+
next if base != t.self
|
|
171
|
+
|
|
172
|
+
base.rrf_finalize
|
|
173
|
+
|
|
174
|
+
# It's important to disable the trace once we've found the end of the base class definition,
|
|
175
|
+
# for performance.
|
|
176
|
+
t.disable
|
|
177
|
+
end
|
|
178
|
+
# :nocov:
|
|
179
|
+
end
|
|
129
180
|
end
|
|
130
181
|
|
|
131
182
|
# Get the configured serializer class.
|
|
@@ -269,7 +320,8 @@ module RESTFramework::BaseControllerMixin
|
|
|
269
320
|
@xml_payload = payload.to_xml if self.class.serialize_to_xml
|
|
270
321
|
end
|
|
271
322
|
@template_logo_text ||= "Rails REST Framework"
|
|
272
|
-
@title ||= self.
|
|
323
|
+
@title ||= self.class.get_title
|
|
324
|
+
@description ||= self.class.description
|
|
273
325
|
@route_props, @route_groups = RESTFramework::Utils.get_routes(
|
|
274
326
|
Rails.application.routes, request
|
|
275
327
|
)
|
|
@@ -5,8 +5,46 @@ require_relative "../filters"
|
|
|
5
5
|
module RESTFramework::BaseModelControllerMixin
|
|
6
6
|
include RESTFramework::BaseControllerMixin
|
|
7
7
|
|
|
8
|
+
RRF_BASE_MODEL_CONTROLLER_CONFIG = {
|
|
9
|
+
# Core attributes related to models.
|
|
10
|
+
model: nil,
|
|
11
|
+
recordset: nil,
|
|
12
|
+
|
|
13
|
+
# Attributes for configuring record fields.
|
|
14
|
+
fields: nil,
|
|
15
|
+
action_fields: nil,
|
|
16
|
+
|
|
17
|
+
# Attributes for finding records.
|
|
18
|
+
find_by_fields: nil,
|
|
19
|
+
find_by_query_param: "find_by",
|
|
20
|
+
|
|
21
|
+
# Attributes for create/update parameters.
|
|
22
|
+
allowed_parameters: nil,
|
|
23
|
+
allowed_action_parameters: nil,
|
|
24
|
+
|
|
25
|
+
# Attributes for the default native serializer.
|
|
26
|
+
native_serializer_config: nil,
|
|
27
|
+
native_serializer_singular_config: nil,
|
|
28
|
+
native_serializer_plural_config: nil,
|
|
29
|
+
native_serializer_only_query_param: "only",
|
|
30
|
+
native_serializer_except_query_param: "except",
|
|
31
|
+
|
|
32
|
+
# Attributes for default model filtering, ordering, and searching.
|
|
33
|
+
filterset_fields: nil,
|
|
34
|
+
ordering_fields: nil,
|
|
35
|
+
ordering_query_param: "ordering",
|
|
36
|
+
ordering_no_reorder: false,
|
|
37
|
+
search_fields: nil,
|
|
38
|
+
search_query_param: "search",
|
|
39
|
+
search_ilike: false,
|
|
40
|
+
|
|
41
|
+
# Other misc attributes.
|
|
42
|
+
create_from_recordset: true, # Option for `recordset.create` vs `Model.create` behavior.
|
|
43
|
+
filter_recordset_before_find: true, # Control if filtering is done before find.
|
|
44
|
+
}
|
|
45
|
+
|
|
8
46
|
module ClassMethods
|
|
9
|
-
IGNORE_VALIDATORS_WITH_KEYS = [:if, :unless]
|
|
47
|
+
IGNORE_VALIDATORS_WITH_KEYS = [:if, :unless].freeze
|
|
10
48
|
|
|
11
49
|
# Get the model for this controller.
|
|
12
50
|
def get_model(from_get_recordset: false)
|
|
@@ -19,8 +57,8 @@ module RESTFramework::BaseModelControllerMixin
|
|
|
19
57
|
rescue NameError
|
|
20
58
|
end
|
|
21
59
|
|
|
22
|
-
# Delegate to the recordset's model, if it's defined.
|
|
23
|
-
unless from_get_recordset
|
|
60
|
+
# Delegate to the recordset's model, if it's defined. This option prevents infinite recursion.
|
|
61
|
+
unless from_get_recordset
|
|
24
62
|
# Instantiate a new controller to get the recordset.
|
|
25
63
|
controller = self.new
|
|
26
64
|
controller.request = ActionController::TestRequest.new
|
|
@@ -34,24 +72,34 @@ module RESTFramework::BaseModelControllerMixin
|
|
|
34
72
|
return nil
|
|
35
73
|
end
|
|
36
74
|
|
|
75
|
+
# Override `get_label` to include ActiveRecord i18n-translated column names.
|
|
76
|
+
def get_label(s)
|
|
77
|
+
return self.get_model.human_attribute_name(s, default: super)
|
|
78
|
+
end
|
|
79
|
+
|
|
37
80
|
# Get metadata about the resource's fields.
|
|
38
81
|
def get_fields_metadata(fields: nil)
|
|
39
82
|
# Get metadata sources.
|
|
40
83
|
model = self.get_model
|
|
41
|
-
fields ||= self.fields || model
|
|
84
|
+
fields ||= self.fields || model.column_names || []
|
|
42
85
|
fields = fields.map(&:to_s)
|
|
43
|
-
columns = model
|
|
44
|
-
column_defaults = model
|
|
45
|
-
attributes = model
|
|
86
|
+
columns = model.columns_hash
|
|
87
|
+
column_defaults = model.column_defaults
|
|
88
|
+
attributes = model._default_attributes
|
|
46
89
|
|
|
47
90
|
return fields.map { |f|
|
|
48
91
|
# Initialize metadata to make the order consistent.
|
|
49
92
|
metadata = {
|
|
50
|
-
type: nil,
|
|
93
|
+
type: nil,
|
|
94
|
+
kind: nil,
|
|
95
|
+
label: self.get_label(f),
|
|
96
|
+
primary_key: nil,
|
|
97
|
+
required: nil,
|
|
98
|
+
read_only: nil,
|
|
51
99
|
}
|
|
52
100
|
|
|
53
101
|
# Determine `primary_key` based on model.
|
|
54
|
-
if model
|
|
102
|
+
if model.primary_key == f
|
|
55
103
|
metadata[:primary_key] = true
|
|
56
104
|
end
|
|
57
105
|
|
|
@@ -59,7 +107,6 @@ module RESTFramework::BaseModelControllerMixin
|
|
|
59
107
|
if column = columns[f]
|
|
60
108
|
metadata[:type] = column.type
|
|
61
109
|
metadata[:required] = true unless column.null
|
|
62
|
-
metadata[:label] = column.human_name.instance_eval { |n| n == "Id" ? "ID" : n }
|
|
63
110
|
metadata[:kind] = "column"
|
|
64
111
|
end
|
|
65
112
|
|
|
@@ -115,79 +162,17 @@ module RESTFramework::BaseModelControllerMixin
|
|
|
115
162
|
|
|
116
163
|
# Get a hash of metadata to be rendered in the `OPTIONS` response. Cache the result.
|
|
117
164
|
def get_options_metadata(fields: nil)
|
|
118
|
-
return super().merge(
|
|
119
|
-
{
|
|
120
|
-
fields: self.get_fields_metadata(fields: fields),
|
|
121
|
-
},
|
|
122
|
-
)
|
|
165
|
+
return super().merge({fields: self.get_fields_metadata(fields: fields)})
|
|
123
166
|
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def self.included(base)
|
|
127
|
-
return unless base.is_a?(Class)
|
|
128
|
-
|
|
129
|
-
RESTFramework::BaseControllerMixin.included(base)
|
|
130
|
-
base.extend(ClassMethods)
|
|
131
|
-
|
|
132
|
-
# Add class attributes (with defaults) unless they already exist.
|
|
133
|
-
{
|
|
134
|
-
# Core attributes related to models.
|
|
135
|
-
model: nil,
|
|
136
|
-
recordset: nil,
|
|
137
|
-
|
|
138
|
-
# Attributes for configuring record fields.
|
|
139
|
-
fields: nil,
|
|
140
|
-
action_fields: nil,
|
|
141
|
-
metadata_fields: nil,
|
|
142
|
-
|
|
143
|
-
# Attributes for finding records.
|
|
144
|
-
find_by_fields: nil,
|
|
145
|
-
find_by_query_param: "find_by",
|
|
146
|
-
|
|
147
|
-
# Attributes for create/update parameters.
|
|
148
|
-
allowed_parameters: nil,
|
|
149
|
-
allowed_action_parameters: nil,
|
|
150
|
-
|
|
151
|
-
# Attributes for the default native serializer.
|
|
152
|
-
native_serializer_config: nil,
|
|
153
|
-
native_serializer_singular_config: nil,
|
|
154
|
-
native_serializer_plural_config: nil,
|
|
155
|
-
native_serializer_only_query_param: "only",
|
|
156
|
-
native_serializer_except_query_param: "except",
|
|
157
|
-
|
|
158
|
-
# Attributes for default model filtering, ordering, and searching.
|
|
159
|
-
filterset_fields: nil,
|
|
160
|
-
ordering_fields: nil,
|
|
161
|
-
ordering_query_param: "ordering",
|
|
162
|
-
ordering_no_reorder: false,
|
|
163
|
-
search_fields: nil,
|
|
164
|
-
search_query_param: "search",
|
|
165
|
-
search_ilike: false,
|
|
166
|
-
|
|
167
|
-
# Other misc attributes.
|
|
168
|
-
create_from_recordset: true, # Option for `recordset.create` vs `Model.create` behavior.
|
|
169
|
-
filter_recordset_before_find: true, # Control if filtering is done before find.
|
|
170
|
-
}.each do |a, default|
|
|
171
|
-
next if base.respond_to?(a)
|
|
172
|
-
|
|
173
|
-
base.class_attribute(a)
|
|
174
|
-
|
|
175
|
-
# Set default manually so we can still support Rails 4. Maybe later we can use the default
|
|
176
|
-
# parameter on `class_attribute`.
|
|
177
|
-
base.send(:"#{a}=", default)
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Actions to run at the end of the class definition.
|
|
181
|
-
TracePoint.trace(:end) do |t|
|
|
182
|
-
next if base != t.self
|
|
183
167
|
|
|
168
|
+
def setup_delegation
|
|
184
169
|
# Delegate extra actions.
|
|
185
|
-
|
|
170
|
+
self.extra_actions&.each do |action, config|
|
|
186
171
|
next unless config.is_a?(Hash) && config[:delegate]
|
|
172
|
+
next unless self.get_model.respond_to?(action)
|
|
187
173
|
|
|
188
|
-
|
|
174
|
+
self.define_method(action) do
|
|
189
175
|
model = self.class.get_model
|
|
190
|
-
next unless model.respond_to?(action)
|
|
191
176
|
|
|
192
177
|
if model.method(action).parameters.last&.first == :keyrest
|
|
193
178
|
return api_response(model.send(action, **params))
|
|
@@ -198,12 +183,12 @@ module RESTFramework::BaseModelControllerMixin
|
|
|
198
183
|
end
|
|
199
184
|
|
|
200
185
|
# Delegate extra member actions.
|
|
201
|
-
|
|
186
|
+
self.extra_member_actions&.each do |action, config|
|
|
202
187
|
next unless config.is_a?(Hash) && config[:delegate]
|
|
188
|
+
next unless self.get_model.method_defined?(action)
|
|
203
189
|
|
|
204
|
-
|
|
190
|
+
self.define_method(action) do
|
|
205
191
|
record = self.get_record
|
|
206
|
-
next unless record.respond_to?(action)
|
|
207
192
|
|
|
208
193
|
if record.method(action).parameters.last&.first == :keyrest
|
|
209
194
|
return api_response(record.send(action, **params))
|
|
@@ -212,10 +197,40 @@ module RESTFramework::BaseModelControllerMixin
|
|
|
212
197
|
end
|
|
213
198
|
end
|
|
214
199
|
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Define any behavior to execute at the end of controller definition.
|
|
203
|
+
# :nocov:
|
|
204
|
+
def rrf_finalize
|
|
205
|
+
super
|
|
206
|
+
self.setup_delegation
|
|
207
|
+
# self.setup_channel
|
|
208
|
+
|
|
209
|
+
if RESTFramework.config.freeze_config
|
|
210
|
+
self::RRF_BASE_MODEL_CONTROLLER_CONFIG.keys.each { |k|
|
|
211
|
+
v = self.send(k)
|
|
212
|
+
v.freeze if v.is_a?(Hash) || v.is_a?(Array)
|
|
213
|
+
}
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
# :nocov:
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def self.included(base)
|
|
220
|
+
return unless base.is_a?(Class)
|
|
221
|
+
|
|
222
|
+
RESTFramework::BaseControllerMixin.included(base)
|
|
223
|
+
base.extend(ClassMethods)
|
|
224
|
+
|
|
225
|
+
# Add class attributes (with defaults) unless they already exist.
|
|
226
|
+
RRF_BASE_MODEL_CONTROLLER_CONFIG.each do |a, default|
|
|
227
|
+
next if base.respond_to?(a)
|
|
215
228
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
229
|
+
base.class_attribute(a)
|
|
230
|
+
|
|
231
|
+
# Set default manually so we can still support Rails 4. Maybe later we can use the default
|
|
232
|
+
# parameter on `class_attribute`.
|
|
233
|
+
base.send(:"#{a}=", default)
|
|
219
234
|
end
|
|
220
235
|
end
|
|
221
236
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require "rails/generators"
|
|
2
2
|
|
|
3
3
|
# Most projects don't have the inflection "REST" as an acronym, so this is a helper class to prevent
|
|
4
|
-
# this generator from being namespaced
|
|
4
|
+
# this generator from being namespaced as `"r_e_s_t_framework"`.
|
|
5
5
|
# :nocov:
|
|
6
6
|
class RESTFrameworkCustomGeneratorControllerNamespace < String
|
|
7
7
|
def camelize
|
|
@@ -39,8 +39,10 @@ module ActionDispatch::Routing
|
|
|
39
39
|
|
|
40
40
|
# Interal interface for routing extra actions.
|
|
41
41
|
def _route_extra_actions(actions, &block)
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
parsed_actions = RESTFramework::Utils.parse_extra_actions(actions)
|
|
43
|
+
|
|
44
|
+
parsed_actions.each do |action, config|
|
|
45
|
+
[config[:methods]].flatten.each do |m|
|
|
44
46
|
public_send(m, config[:path], action: action, **(config[:kwargs] || {}))
|
|
45
47
|
end
|
|
46
48
|
yield if block_given?
|
|
@@ -84,17 +86,13 @@ module ActionDispatch::Routing
|
|
|
84
86
|
public_send(resource_method, name, except: skip, **kwargs) do
|
|
85
87
|
if controller_class.respond_to?(:extra_member_actions)
|
|
86
88
|
member do
|
|
87
|
-
self._route_extra_actions(
|
|
88
|
-
RESTFramework::Utils.parse_extra_actions(controller_class.extra_member_actions),
|
|
89
|
-
)
|
|
89
|
+
self._route_extra_actions(controller_class.extra_member_actions)
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
92
|
|
|
93
93
|
collection do
|
|
94
94
|
# Route extra controller-defined actions.
|
|
95
|
-
self._route_extra_actions(
|
|
96
|
-
RESTFramework::Utils.parse_extra_actions(controller_class.extra_actions),
|
|
97
|
-
)
|
|
95
|
+
self._route_extra_actions(controller_class.extra_actions)
|
|
98
96
|
|
|
99
97
|
# Route extra RRF-defined actions.
|
|
100
98
|
RESTFramework::RRF_BUILTIN_ACTIONS.each do |action, methods|
|
|
@@ -155,9 +153,7 @@ module ActionDispatch::Routing
|
|
|
155
153
|
|
|
156
154
|
collection do
|
|
157
155
|
# Route extra controller-defined actions.
|
|
158
|
-
self._route_extra_actions(
|
|
159
|
-
RESTFramework::Utils.parse_extra_actions(controller_class.extra_actions),
|
|
160
|
-
)
|
|
156
|
+
self._route_extra_actions(controller_class.extra_actions)
|
|
161
157
|
|
|
162
158
|
# Route extra RRF-defined actions.
|
|
163
159
|
RESTFramework::RRF_BUILTIN_ACTIONS.each do |action, methods|
|
|
@@ -114,7 +114,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
|
|
|
114
114
|
return subcfg unless subcfg
|
|
115
115
|
|
|
116
116
|
if subcfg.is_a?(Array)
|
|
117
|
-
subcfg = subcfg.
|
|
117
|
+
subcfg = subcfg.map(&:to_sym)
|
|
118
118
|
|
|
119
119
|
if add
|
|
120
120
|
# Only add fields which are not already included.
|
data/lib/rest_framework/utils.rb
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
module RESTFramework::Utils
|
|
2
2
|
HTTP_METHOD_ORDERING = %w(GET POST PUT PATCH DELETE OPTIONS HEAD)
|
|
3
3
|
|
|
4
|
-
# Convert `extra_actions` hash to a consistent format: `{path:, methods:, kwargs:}
|
|
5
|
-
|
|
4
|
+
# Convert `extra_actions` hash to a consistent format: `{path:, methods:, kwargs:}`, and
|
|
5
|
+
# additional metadata fields.
|
|
6
|
+
#
|
|
7
|
+
# If a controller is provided, labels will be added to any metadata fields.
|
|
8
|
+
def self.parse_extra_actions(extra_actions, controller: nil)
|
|
6
9
|
return (extra_actions || {}).map { |k, v|
|
|
7
10
|
path = k
|
|
8
11
|
|
|
9
12
|
# Convert structure to path/methods/kwargs.
|
|
10
13
|
if v.is_a?(Hash) # Allow kwargs to be used to define path differently from the key.
|
|
14
|
+
# Symbolize keys (which also makes a copy so we don't mutate the original).
|
|
11
15
|
v = v.symbolize_keys
|
|
16
|
+
methods = v.delete(:methods)
|
|
12
17
|
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
# First, remove any metadata keys.
|
|
19
|
+
delegate = v.delete(:delegate)
|
|
20
|
+
fields = v.delete(:fields)
|
|
21
|
+
|
|
22
|
+
# Add label to fields.
|
|
23
|
+
if controller && fields
|
|
24
|
+
fields = fields.map { |f| [f, {}] }.to_h if f.is_a?(Array)
|
|
25
|
+
fields&.each do |field, cfg|
|
|
26
|
+
cfg[:label] = controller.get_label(field)
|
|
27
|
+
end
|
|
18
28
|
end
|
|
19
29
|
|
|
20
30
|
# Override path if it's provided.
|
|
@@ -23,14 +33,24 @@ module RESTFramework::Utils
|
|
|
23
33
|
end
|
|
24
34
|
|
|
25
35
|
# Pass any further kwargs to the underlying Rails interface.
|
|
26
|
-
kwargs = v.presence
|
|
27
|
-
elsif v.is_a?(
|
|
28
|
-
methods = [
|
|
36
|
+
kwargs = v.presence
|
|
37
|
+
elsif v.is_a?(Array) && v.length == 1
|
|
38
|
+
methods = v[0]
|
|
29
39
|
else
|
|
30
40
|
methods = v
|
|
31
41
|
end
|
|
32
42
|
|
|
33
|
-
[
|
|
43
|
+
next [
|
|
44
|
+
k,
|
|
45
|
+
{
|
|
46
|
+
path: path,
|
|
47
|
+
methods: methods,
|
|
48
|
+
kwargs: kwargs,
|
|
49
|
+
delegate: delegate,
|
|
50
|
+
fields: fields,
|
|
51
|
+
type: :extra,
|
|
52
|
+
}.compact,
|
|
53
|
+
]
|
|
34
54
|
}.to_h
|
|
35
55
|
end
|
|
36
56
|
|
|
@@ -108,4 +128,13 @@ module RESTFramework::Utils
|
|
|
108
128
|
[request.params[:controller] == c ? 0 : 1, c.count("/"), c]
|
|
109
129
|
}.to_h
|
|
110
130
|
end
|
|
131
|
+
|
|
132
|
+
# Custom inflector for RESTful controllers.
|
|
133
|
+
def self.inflect(s, acronyms=nil)
|
|
134
|
+
acronyms&.each do |acronym|
|
|
135
|
+
s = s.gsub(/\b#{acronym}\b/i, acronym)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
return s
|
|
139
|
+
end
|
|
111
140
|
end
|
data/lib/rest_framework.rb
CHANGED
|
@@ -7,12 +7,37 @@ module RESTFramework
|
|
|
7
7
|
BUILTIN_MEMBER_ACTIONS = {
|
|
8
8
|
show: :get,
|
|
9
9
|
edit: :get,
|
|
10
|
-
update: [:put, :patch],
|
|
10
|
+
update: [:put, :patch].freeze,
|
|
11
11
|
destroy: :delete,
|
|
12
12
|
}.freeze
|
|
13
13
|
RRF_BUILTIN_ACTIONS = {
|
|
14
14
|
options: :options,
|
|
15
15
|
}.freeze
|
|
16
|
+
|
|
17
|
+
# Global configuration should be kept minimal, as controller-level configurations allows multiple
|
|
18
|
+
# APIs to be defined to behave differently.
|
|
19
|
+
class Config
|
|
20
|
+
# Do not run `rrf_finalize` on controllers automatically using a `TracePoint` hook. This is a
|
|
21
|
+
# performance option and must be global because we have to determine this before any
|
|
22
|
+
# controller-specific configuration is set. If this is set to `true`, then you must manually
|
|
23
|
+
# call `rrf_finalize` after any configuration on each controller that needs to participate
|
|
24
|
+
# in:
|
|
25
|
+
# - Model delegation, for the helper methods to be defined dynamically.
|
|
26
|
+
# - Websockets, for `::Channel` class to be defined dynamically.
|
|
27
|
+
# - Controller configuration finalization.
|
|
28
|
+
attr_accessor :disable_auto_finalize
|
|
29
|
+
|
|
30
|
+
# Freeze configuration attributes during finalization to prevent accidental mutation.
|
|
31
|
+
attr_accessor :freeze_config
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.config
|
|
35
|
+
return @config ||= Config.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.configure
|
|
39
|
+
yield(self.config)
|
|
40
|
+
end
|
|
16
41
|
end
|
|
17
42
|
|
|
18
43
|
require_relative "rest_framework/controller_mixins"
|
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.7.1
|
|
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:
|
|
11
|
+
date: 2023-01-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|