rest_framework 1.0.0.rc1 → 1.0.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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -2
  3. data/VERSION +1 -1
  4. data/app/views/rest_framework/_head.html.erb +3 -1
  5. data/app/views/rest_framework/_heading.html.erb +1 -0
  6. data/app/views/rest_framework/head/_extra.html.erb +0 -0
  7. data/app/views/rest_framework/heading/_actions.html.erb +2 -1
  8. data/app/views/rest_framework/heading/_extra.html.erb +0 -0
  9. data/app/views/rest_framework/heading/actions/_extra.html.erb +0 -0
  10. data/app/views/rest_framework/routes_and_forms/_html_form.html.erb +2 -2
  11. data/lib/rest_framework/errors/nil_passed_to_render_api_error.rb +1 -1
  12. data/lib/rest_framework/errors/unknown_model_error.rb +1 -1
  13. data/lib/rest_framework/filters/ordering_filter.rb +3 -3
  14. data/lib/rest_framework/filters/query_filter.rb +14 -14
  15. data/lib/rest_framework/filters/ransack_filter.rb +1 -1
  16. data/lib/rest_framework/filters/search_filter.rb +3 -3
  17. data/lib/rest_framework/generators/controller_generator.rb +2 -2
  18. data/lib/rest_framework/mixins/base_controller_mixin.rb +34 -33
  19. data/lib/rest_framework/mixins/bulk_model_controller_mixin.rb +8 -15
  20. data/lib/rest_framework/mixins/model_controller_mixin.rb +91 -65
  21. data/lib/rest_framework/paginators/page_number_paginator.rb +6 -6
  22. data/lib/rest_framework/routers.rb +10 -16
  23. data/lib/rest_framework/serializers/active_model_serializer_adapter_factory.rb +4 -2
  24. data/lib/rest_framework/serializers/base_serializer.rb +7 -5
  25. data/lib/rest_framework/serializers/native_serializer.rb +82 -129
  26. data/lib/rest_framework/utils.rb +27 -26
  27. data/lib/rest_framework/version.rb +1 -1
  28. data/lib/rest_framework.rb +24 -17
  29. data/vendor/assets/javascripts/rest_framework/external.min.js +4 -4
  30. data/vendor/assets/stylesheets/rest_framework/external.min.css +3 -3
  31. metadata +8 -5
@@ -1,13 +1,21 @@
1
1
  # This serializer uses `.serializable_hash` to convert objects to Ruby primitives (with the
2
2
  # top-level being either an array or a hash).
3
3
  class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers::BaseSerializer
4
+ EXTRACT_FROM_QUERY = ->(p, controller) {
5
+ return Set[] if p.blank?
6
+ (
7
+ controller.request&.query_parameters&.[](p).presence&.split(",")&.map { |x|
8
+ x.strip.presence
9
+ }&.compact || []
10
+ ).to_set
11
+ }
4
12
  class_attribute :config
5
13
  class_attribute :singular_config
6
14
  class_attribute :plural_config
7
15
  class_attribute :action_config
8
16
 
9
17
  # Accept/ignore `*args` to be compatible with the `ActiveModel::Serializer#initialize` signature.
10
- def initialize(object=nil, *args, many: nil, model: nil, **kwargs)
18
+ def initialize(object = nil, *args, many: nil, model: nil, **kwargs)
11
19
  super(object, *args, **kwargs)
12
20
 
13
21
  if many.nil?
@@ -26,28 +34,66 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
26
34
  @model ||= @controller.class.get_model if @controller
27
35
  end
28
36
 
29
- # Get controller action, if possible.
30
- def get_action
31
- return @controller&.action_name&.to_sym
37
+ def action
38
+ @action ||= @controller&.action_name&.to_sym
32
39
  end
33
40
 
34
- # Get a locally defined native serializer configuration, if one is defined.
35
- def get_local_native_serializer_config
36
- action = self.get_action
41
+ def fields
42
+ return @fields if defined?(@fields)
43
+ return nil unless base_fields = @controller&.get_fields
44
+
45
+ only_param = @controller.class.native_serializer_only_query_param
46
+ except_param = @controller.class.native_serializer_except_query_param
47
+ include_param = @controller.class.native_serializer_include_query_param
48
+ exclude_param = @controller.class.native_serializer_exclude_query_param
49
+
50
+ only = EXTRACT_FROM_QUERY.call(only_param, @controller)
51
+ except = EXTRACT_FROM_QUERY.call(except_param, @controller)
52
+ include = EXTRACT_FROM_QUERY.call(include_param, @controller)
53
+ exclude = EXTRACT_FROM_QUERY.call(exclude_param, @controller)
54
+
55
+ field_configuration = @controller.class.field_configuration
56
+ @fields = base_fields.select do |f|
57
+ cfg = field_configuration[f]
58
+
59
+ # We never serialize write-only fields.
60
+ next false if cfg[:write_only]
61
+
62
+ # We never serialize `hidden_from_index` fields for collections as this is a performance
63
+ # option.
64
+ next false if cfg[:hidden_from_index] && @many
65
+
66
+ # Explicitly excluded fields should never be serialized.
67
+ next false if f.in?(except) || f.in?(exclude)
68
+
69
+ # Hidden fields must be in `only` or `include` to be serialized; for non-hidden fields, either
70
+ # `only` must be empty, or the field must be in `only` or `include`.
71
+ if cfg[:hidden]
72
+ next true if f.in?(only) || f.in?(include)
73
+ elsif only.empty? || f.in?(only) || f.in?(include)
74
+ next true
75
+ end
37
76
 
38
- if action && self.action_config
77
+ next false
78
+ end
79
+
80
+ @fields
81
+ end
82
+
83
+ def get_local_native_serializer_config
84
+ if (action = self.action) && (cfg = action_config)
39
85
  # Index action should use :list serializer config if :index is not provided.
40
- action = :list if action == :index && !self.action_config.key?(:index)
86
+ action = :list if action == :index && !cfg.key?(:index)
41
87
 
42
- return self.action_config[action] if self.action_config[action]
88
+ return cfg[action] if cfg[action]
43
89
  end
44
90
 
45
91
  # No action_config, so try singular/plural config if explicitly instructed to via @many.
46
92
  return self.plural_config if @many == true && self.plural_config
47
93
  return self.singular_config if @many == false && self.singular_config
48
94
 
49
- # Lastly, try returning the default config, or singular/plural config in that order.
50
- return self.config || self.singular_config || self.plural_config
95
+ # Lastly, try returning the default config.
96
+ self.config
51
97
  end
52
98
 
53
99
  # Get a native serializer configuration from the controller.
@@ -60,105 +106,12 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
60
106
  controller_serializer = @controller.class.native_serializer_singular_config
61
107
  end
62
108
 
63
- return controller_serializer || @controller.class.native_serializer_config
64
- end
65
-
66
- # Filter a single subconfig for specific keys. By default, keys from `fields` are removed from the
67
- # provided `subcfg`. There are two (mutually exclusive) options to adjust the behavior:
68
- #
69
- # `add`: Add any `fields` to the `subcfg` which aren't already in the `subcfg`.
70
- # `only`: Remove any values found in the `subcfg` not in `fields`.
71
- def self.filter_subcfg(subcfg, fields:, add: false, only: false)
72
- raise "`add` and `only` conflict with one another" if add && only
73
-
74
- # Don't process nil `subcfg`s.
75
- return subcfg unless subcfg
76
-
77
- if subcfg.is_a?(Array)
78
- subcfg = subcfg.map(&:to_sym)
79
-
80
- if add
81
- # Only add fields which are not already included.
82
- subcfg += fields - subcfg
83
- elsif only
84
- subcfg.select! { |c| c.in?(fields) }
85
- else
86
- subcfg -= fields
87
- end
88
- elsif subcfg.is_a?(Hash)
89
- subcfg = subcfg.symbolize_keys
90
-
91
- if add
92
- # Add doesn't make sense in a hash context since we wouldn't know the values.
93
- elsif only
94
- subcfg.select! { |k, _v| k.in?(fields) }
95
- else
96
- subcfg.reject! { |k, _v| k.in?(fields) }
97
- end
98
- else # Subcfg is a single element (assume string/symbol).
99
- subcfg = subcfg.to_sym
100
-
101
- if add
102
- subcfg = subcfg.in?(fields) ? fields : [subcfg, *fields]
103
- elsif only
104
- subcfg = subcfg.in?(fields) ? subcfg : []
105
- else
106
- subcfg = subcfg.in?(fields) ? [] : subcfg
107
- end
108
- end
109
-
110
- return subcfg
111
- end
112
-
113
- # Filter out configuration properties based on the :except/:only query parameters.
114
- def filter_from_request(cfg)
115
- return cfg unless @controller
116
-
117
- except_param = @controller.class.native_serializer_except_query_param
118
- only_param = @controller.class.native_serializer_only_query_param
119
- if except_param && except = @controller.request&.query_parameters&.[](except_param).presence
120
- if except = except.split(",").map(&:strip).map(&:to_sym).presence
121
- # Filter `only`, `except` (additive), `include`, `methods`, and `serializer_methods`.
122
- if cfg[:only]
123
- cfg[:only] = self.class.filter_subcfg(cfg[:only], fields: except)
124
- elsif cfg[:except]
125
- cfg[:except] = self.class.filter_subcfg(cfg[:except], fields: except, add: true)
126
- else
127
- cfg[:except] = except
128
- end
129
-
130
- cfg[:include] = self.class.filter_subcfg(cfg[:include], fields: except)
131
- cfg[:methods] = self.class.filter_subcfg(cfg[:methods], fields: except)
132
- cfg[:serializer_methods] = self.class.filter_subcfg(
133
- cfg[:serializer_methods], fields: except
134
- )
135
- cfg[:includes_map] = self.class.filter_subcfg(cfg[:includes_map], fields: except)
136
- end
137
- elsif only_param && only = @controller.request&.query_parameters&.[](only_param).presence
138
- if only = only.split(",").map(&:strip).map(&:to_sym).presence
139
- # Filter `only`, `include`, and `methods`. Adding anything to `except` is not needed,
140
- # because any configuration there takes precedence over `only`.
141
- if cfg[:only]
142
- cfg[:only] = self.class.filter_subcfg(cfg[:only], fields: only, only: true)
143
- else
144
- cfg[:only] = only
145
- end
146
-
147
- cfg[:include] = self.class.filter_subcfg(cfg[:include], fields: only, only: true)
148
- cfg[:methods] = self.class.filter_subcfg(cfg[:methods], fields: only, only: true)
149
- cfg[:serializer_methods] = self.class.filter_subcfg(
150
- cfg[:serializer_methods], fields: only, only: true
151
- )
152
- cfg[:includes_map] = self.class.filter_subcfg(cfg[:includes_map], fields: only, only: true)
153
- end
154
- end
155
-
156
- return cfg
109
+ controller_serializer || @controller.class.native_serializer_config
157
110
  end
158
111
 
159
112
  # Get the associations limit from the controller.
160
- def _get_associations_limit
161
- return @_get_associations_limit if defined?(@_get_associations_limit)
113
+ def _associations_limit
114
+ return @_associations_limit if defined?(@_associations_limit)
162
115
 
163
116
  limit = @controller&.class&.native_serializer_associations_limit
164
117
 
@@ -174,11 +127,11 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
174
127
  end
175
128
  end
176
129
 
177
- return @_get_associations_limit = limit
130
+ @_associations_limit = limit
178
131
  end
179
132
 
180
133
  # Get a serializer configuration from the controller. `@controller` and `@model` must be set.
181
- def _get_controller_serializer_config(fields)
134
+ def _get_controller_serializer_config
182
135
  columns = []
183
136
  includes = {}
184
137
  methods = []
@@ -194,7 +147,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
194
147
  reflections = @model.reflections
195
148
  attachment_reflections = @model.attachment_reflections
196
149
 
197
- fields.each do |f|
150
+ self.fields.each do |f|
198
151
  field_config = @controller.class.field_configuration[f]
199
152
  next if field_config[:write_only]
200
153
 
@@ -210,13 +163,13 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
210
163
  sub_methods << sf
211
164
  end
212
165
  end
213
- sub_config = {only: sub_columns, methods: sub_methods}
166
+ sub_config = { only: sub_columns, methods: sub_methods }
214
167
 
215
168
  # Apply certain rules regarding collection associations.
216
169
  if ref.collection?
217
170
  # If we need to limit the number of serialized association records, then dynamically add a
218
171
  # serializer method to do so.
219
- if limit = self._get_associations_limit
172
+ if limit = self._associations_limit
220
173
  serializer_methods[f] = f
221
174
  self.define_singleton_method(f) do |record|
222
175
  next record.send(f).limit(limit).as_json(**sub_config)
@@ -257,7 +210,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
257
210
  # ActiveStorage Integration: Define attachment serializer method.
258
211
  if ref.macro == :has_one_attached
259
212
  serializer_methods[f] = f
260
- includes_map[f] = {"#{f}_attachment": :blob}
213
+ includes_map[f] = { "#{f}_attachment": :blob }
261
214
  self.define_singleton_method(f) do |record|
262
215
  attached = record.send(f)
263
216
  next attached.attachment ? {
@@ -268,7 +221,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
268
221
  end
269
222
  elsif ref.macro == :has_many_attached
270
223
  serializer_methods[f] = f
271
- includes_map[f] = {"#{f}_attachments": :blob}
224
+ includes_map[f] = { "#{f}_attachments": :blob }
272
225
  self.define_singleton_method(f) do |record|
273
226
  # Iterating the collection yields attachment objects.
274
227
  next record.send(f).map { |a|
@@ -288,7 +241,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
288
241
  end
289
242
  end
290
243
 
291
- return {
244
+ {
292
245
  only: columns,
293
246
  include: includes,
294
247
  methods: methods,
@@ -312,29 +265,29 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
312
265
  end
313
266
 
314
267
  # If the config wasn't determined, build a serializer config from controller fields.
315
- if @model && fields = @controller&.get_fields
316
- return self._get_controller_serializer_config(fields.deep_dup)
268
+ if @model && self.fields
269
+ return self._get_controller_serializer_config
317
270
  end
318
271
 
319
272
  # By default, pass an empty configuration, using the default Rails serializer.
320
- return {}
273
+ {}
321
274
  end
322
275
 
323
- # Get a configuration passable to `serializable_hash` for the object, filtered if required.
276
+ # Get a configuration passable to `serializable_hash` for the object.
324
277
  def get_serializer_config
325
- return self.filter_from_request(self.get_raw_serializer_config)
278
+ self.get_raw_serializer_config
326
279
  end
327
280
 
328
281
  # Serialize a single record and merge results of `serializer_methods`.
329
282
  def _serialize(record, config, serializer_methods)
330
283
  # Ensure serializer_methods is either falsy, or a hash.
331
284
  if serializer_methods && !serializer_methods.is_a?(Hash)
332
- serializer_methods = [serializer_methods].flatten.map { |m| [m, m] }.to_h
285
+ serializer_methods = [ serializer_methods ].flatten.map { |m| [ m, m ] }.to_h
333
286
  end
334
287
 
335
288
  # Merge serialized record with any serializer method results.
336
- return record.serializable_hash(config).merge(
337
- serializer_methods&.map { |m, k| [k.to_sym, self.send(m, record)] }.to_h,
289
+ record.serializable_hash(config).merge(
290
+ serializer_methods&.map { |m, k| [ k.to_sym, self.send(m, record) ] }.to_h,
338
291
  )
339
292
  end
340
293
 
@@ -352,29 +305,29 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
352
305
  return @object.map { |r| self._serialize(r, config, serializer_methods) }
353
306
  end
354
307
 
355
- return self._serialize(@object, config, serializer_methods)
308
+ self._serialize(@object, config, serializer_methods)
356
309
  end
357
310
 
358
311
  # Allow a serializer instance to be used as a hash directly in a nested serializer config.
359
312
  def [](key)
360
313
  @_nested_config ||= self.get_serializer_config
361
- return @_nested_config[key]
314
+ @_nested_config[key]
362
315
  end
363
316
 
364
317
  def []=(key, value)
365
318
  @_nested_config ||= self.get_serializer_config
366
- return @_nested_config[key] = value
319
+ @_nested_config[key] = value
367
320
  end
368
321
 
369
322
  # Allow a serializer class to be used as a hash directly in a nested serializer config.
370
323
  def self.[](key)
371
324
  @_nested_config ||= self.new.get_serializer_config
372
- return @_nested_config[key]
325
+ @_nested_config[key]
373
326
  end
374
327
 
375
328
  def self.[]=(key, value)
376
329
  @_nested_config ||= self.new.get_serializer_config
377
- return @_nested_config[key] = value
330
+ @_nested_config[key] = value
378
331
  end
379
332
  end
380
333
 
@@ -1,9 +1,9 @@
1
1
  module RESTFramework::Utils
2
- HTTP_VERB_ORDERING = %w(GET POST PUT PATCH DELETE OPTIONS HEAD)
2
+ HTTP_VERB_ORDERING = %w[GET POST PUT PATCH DELETE OPTIONS HEAD]
3
3
 
4
4
  # Convert `extra_actions` hash to a consistent format: `{path:, methods:, metadata:, kwargs:}`.
5
5
  def self.parse_extra_actions(extra_actions)
6
- return (extra_actions || {}).map { |k, v|
6
+ (extra_actions || {}).map { |k, v|
7
7
  path = k
8
8
  kwargs = {}
9
9
 
@@ -13,7 +13,7 @@ module RESTFramework::Utils
13
13
  v = v.symbolize_keys
14
14
 
15
15
  # Cast method/methods to an array.
16
- methods = [v.delete(:methods), v.delete(:method)].flatten.compact
16
+ methods = [ v.delete(:methods), v.delete(:method) ].flatten.compact
17
17
 
18
18
  # Override path if it's provided.
19
19
  if v.key?(:path)
@@ -26,7 +26,7 @@ module RESTFramework::Utils
26
26
  # Pass any further kwargs to the underlying Rails interface.
27
27
  kwargs = v
28
28
  else
29
- methods = [v].flatten
29
+ methods = [ v ].flatten
30
30
  end
31
31
 
32
32
  next [
@@ -41,10 +41,11 @@ module RESTFramework::Utils
41
41
  }.to_h
42
42
  end
43
43
 
44
- # Get actions which should be skipped for a given controller.
45
- def self.get_skipped_builtin_actions(controller_class)
46
- return (
47
- RESTFramework::BUILTIN_ACTIONS.keys + RESTFramework::BUILTIN_MEMBER_ACTIONS.keys
44
+ def self.get_skipped_builtin_actions(controller_class, singular)
45
+ (
46
+ (
47
+ RESTFramework::BUILTIN_ACTIONS.keys - (singular ? [ :index ] : [])
48
+ ) + RESTFramework::BUILTIN_MEMBER_ACTIONS.keys
48
49
  ).reject do |action|
49
50
  controller_class.method_defined?(action)
50
51
  end
@@ -58,7 +59,7 @@ module RESTFramework::Utils
58
59
  # Normalize a path pattern by replacing URL params with generic placeholder, and removing the
59
60
  # `(.:format)` at the end.
60
61
  def self.comparable_path(path)
61
- return path.gsub("(.:format)", "").gsub(/:[0-9A-Za-z_-]+/, ":x")
62
+ path.gsub("(.:format)", "").gsub(/:[0-9A-Za-z_-]+/, ":x")
62
63
  end
63
64
 
64
65
  # Show routes under a controller action; used for the browsable API.
@@ -74,7 +75,7 @@ module RESTFramework::Utils
74
75
 
75
76
  # Return routes that match our current route subdomain/pattern, grouped by controller. We
76
77
  # precompute certain properties of the route for performance.
77
- return application_routes.routes.select { |r|
78
+ application_routes.routes.select { |r|
78
79
  # We `select` first to avoid unnecessarily calculating metadata for routes we don't even want
79
80
  # to show.
80
81
  (r.defaults[:subdomain].blank? || r.defaults[:subdomain] == request.subdomain) &&
@@ -100,7 +101,7 @@ module RESTFramework::Utils
100
101
  verb: r.verb,
101
102
  path: path,
102
103
  path_with_params: r.format(
103
- r.required_parts.each_with_index.map { |p, i| [p, path_params[i]] }.to_h,
104
+ r.required_parts.each_with_index.map { |p, i| [ p, path_params[i] ] }.to_h,
104
105
  ),
105
106
  relative_path: relative_path,
106
107
  concat_path: concat_path,
@@ -125,17 +126,17 @@ module RESTFramework::Utils
125
126
  # Sort the controller groups by current controller first, then alphanumerically.
126
127
  # Note: Use `controller_path` instead of `params[:controller]` to avoid re-raising a
127
128
  # `ActionDispatch::Http::Parameters::ParseError` exception.
128
- [request.controller_class.controller_path == c ? 0 : 1, c]
129
+ [ request.controller_class.controller_path == c ? 0 : 1, c ]
129
130
  }.to_h
130
131
  end
131
132
 
132
133
  # Custom inflector for RESTful controllers.
133
- def self.inflect(s, acronyms=nil)
134
+ def self.inflect(s, acronyms = nil)
134
135
  acronyms&.each do |acronym|
135
136
  s = s.gsub(/\b#{acronym}\b/i, acronym)
136
137
  end
137
138
 
138
- return s
139
+ s
139
140
  end
140
141
 
141
142
  # Parse fields hashes.
@@ -153,12 +154,12 @@ module RESTFramework::Utils
153
154
  parsed_fields -= h[:except].map(&:to_s) if h[:except]
154
155
 
155
156
  # Warn for any unknown keys.
156
- (h.keys - [:only, :except, :include, :exclude]).each do |k|
157
- Rails.logger.warn("RRF: Unknown key in fields hash: #{k}")
157
+ (h.keys - [ :only, :except, :include, :exclude ]).each do |k|
158
+ Rails.logger.warn("RRF: Unknown key in fields hash: #{k}.")
158
159
  end
159
160
 
160
161
  # We should always return strings, not symbols.
161
- return parsed_fields.map(&:to_s)
162
+ parsed_fields.map(&:to_s)
162
163
  end
163
164
 
164
165
  # Get the fields for a given model, including not just columns (which includes
@@ -181,7 +182,7 @@ module RESTFramework::Utils
181
182
  # Associations:
182
183
  associations = model.reflections.map { |association, ref|
183
184
  # Ignore associations for which we have custom integrations.
184
- if ref.class_name.in?(%w(ActionText::RichText ActiveStorage::Attachment ActiveStorage::Blob))
185
+ if ref.class_name.in?(%w[ActionText::RichText ActiveStorage::Attachment ActiveStorage::Blob])
185
186
  next nil
186
187
  end
187
188
 
@@ -194,29 +195,29 @@ module RESTFramework::Utils
194
195
  next association
195
196
  }.compact
196
197
 
197
- return base_fields + associations + atf + asf
198
+ base_fields + associations + atf + asf
198
199
  end
199
200
 
200
201
  # Get the sub-fields that may be serialized and filtered/ordered for a reflection.
201
202
  def self.sub_fields_for(ref)
202
203
  if !ref.polymorphic? && model = ref.klass
203
- sub_fields = [model.primary_key].flatten.compact
204
+ sub_fields = [ model.primary_key ].flatten.compact
204
205
  label_fields = RESTFramework.config.label_fields
205
206
 
206
- # Preferrably find a database column to use as label.
207
+ # Preferably find a database column to use as label.
207
208
  if match = label_fields.find { |f| f.in?(model.column_names) }
208
- return sub_fields + [match]
209
+ return sub_fields + [ match ]
209
210
  end
210
211
 
211
212
  # Otherwise, find a method.
212
213
  if match = label_fields.find { |f| model.method_defined?(f) }
213
- return sub_fields + [match]
214
+ return sub_fields + [ match ]
214
215
  end
215
216
 
216
217
  return sub_fields
217
218
  end
218
219
 
219
- return ["id", "name"]
220
+ [ "id", "name" ]
220
221
  end
221
222
 
222
223
  # Get a field's id/ids variation.
@@ -229,7 +230,7 @@ module RESTFramework::Utils
229
230
  return reflection.foreign_key
230
231
  end
231
232
 
232
- return nil
233
+ nil
233
234
  end
234
235
 
235
236
  # Wrap a serializer with an adapter if it is an ActiveModel::Serializer.
@@ -238,6 +239,6 @@ module RESTFramework::Utils
238
239
  return RESTFramework::ActiveModelSerializerAdapterFactory.for(s)
239
240
  end
240
241
 
241
- return s
242
+ s
242
243
  end
243
244
  end
@@ -19,7 +19,7 @@ module RESTFramework
19
19
  end
20
20
 
21
21
  # No VERSION file, so version is unknown.
22
- return UNKNOWN
22
+ UNKNOWN
23
23
  end
24
24
 
25
25
  def self.stamp_version
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RESTFramework
4
+ BUILTIN_FORM_ACTIONS = %i[new edit].freeze
4
5
  BUILTIN_ACTIONS = {
5
6
  index: :get,
6
7
  new: :get,
@@ -9,14 +10,14 @@ module RESTFramework
9
10
  BUILTIN_MEMBER_ACTIONS = {
10
11
  show: :get,
11
12
  edit: :get,
12
- update: [:put, :patch].freeze,
13
+ update: [ :put, :patch ].freeze,
13
14
  destroy: :delete,
14
15
  }.freeze
15
16
  RRF_BUILTIN_ACTIONS = {
16
17
  options: :options,
17
18
  }.freeze
18
19
  RRF_BUILTIN_BULK_ACTIONS = {
19
- update_all: [:put, :patch].freeze,
20
+ update_all: [ :put, :patch ].freeze,
20
21
  destroy_all: :delete,
21
22
  }.freeze
22
23
 
@@ -35,12 +36,12 @@ module RESTFramework
35
36
  EXTERNAL_ASSETS = {
36
37
  # Bootstrap
37
38
  "bootstrap.min.css" => {
38
- url: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css",
39
- sri: "sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH",
39
+ url: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css",
40
+ sri: "sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr",
40
41
  },
41
42
  "bootstrap.min.js" => {
42
- url: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js",
43
- sri: "sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz",
43
+ url: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js",
44
+ sri: "sha384-ndDqU0Gzau9qJ1lfW4pNLlhNTkCfHzAVBReH9diLvGRem5+R9g2FzA8ZGN954O5Q",
44
45
  },
45
46
 
46
47
  # Bootstrap Icons
@@ -65,12 +66,12 @@ module RESTFramework
65
66
  "highlight-a11y-dark.min.css" => {
66
67
  url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/styles/a11y-dark.min.css",
67
68
  sri: "sha512-Vj6gPCk8EZlqnoveEyuGyYaWZ1+jyjMPg8g4shwyyNlRQl6d3L9At02ZHQr5K6s5duZl/+YKMnM3/8pDhoUphg==",
68
- extra_tag_attrs: {class: "rrf-dark-mode"},
69
+ extra_tag_attrs: { class: "rrf-dark-mode" },
69
70
  },
70
71
  "highlight-a11y-light.min.css" => {
71
72
  url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/styles/a11y-light.min.css",
72
73
  sri: "sha512-WDk6RzwygsN9KecRHAfm9HTN87LQjqdygDmkHSJxVkVI7ErCZ8ZWxP6T8RvBujY1n2/E4Ac+bn2ChXnp5rnnHA==",
73
- extra_tag_attrs: {class: "rrf-light-mode"},
74
+ extra_tag_attrs: { class: "rrf-light-mode" },
74
75
  },
75
76
 
76
77
  # NeatJSON
@@ -117,7 +118,7 @@ module RESTFramework
117
118
  raise "Unknown asset extension: #{ext}."
118
119
  end
119
120
 
120
- [name, cfg]
121
+ [ name, cfg ]
121
122
  }.to_h.freeze
122
123
  # rubocop:enable Layout/LineLength
123
124
 
@@ -126,9 +127,9 @@ module RESTFramework
126
127
  # Global configuration should be kept minimal, as controller-level configurations allows multiple
127
128
  # APIs to be defined to behave differently.
128
129
  class Config
129
- DEFAULT_LABEL_FIELDS = %w(name label login title email username url).freeze
130
- DEFAULT_SEARCH_COLUMNS = DEFAULT_LABEL_FIELDS + %w(description note).freeze
131
- DEFAULT_EXCLUDE_BODY_FIELDS = %w[
130
+ DEFAULT_LABEL_FIELDS = %w[name label login title email username url].freeze
131
+ DEFAULT_SEARCH_COLUMNS = DEFAULT_LABEL_FIELDS + %w[description note].freeze
132
+ DEFAULT_READ_ONLY_FIELDS = %w[
132
133
  created_at
133
134
  created_by
134
135
  created_by_id
@@ -139,6 +140,10 @@ module RESTFramework
139
140
  utf8
140
141
  authenticity_token
141
142
  ].freeze
143
+ DEFAULT_WRITE_ONLY_FIELDS = %w[
144
+ password
145
+ password_confirmation
146
+ ].freeze
142
147
 
143
148
  # Permits use of `render(api: obj)` syntax over `render_api(obj)`; `true` by default.
144
149
  attr_accessor :register_api_renderer
@@ -172,8 +177,9 @@ module RESTFramework
172
177
  # The default search columns to use when generating search filters.
173
178
  attr_accessor :search_columns
174
179
 
175
- # The default list of fields to exclude from the body of the request.
176
- attr_accessor :exclude_body_fields
180
+ # Helper to set global read/write only fields.
181
+ attr_accessor :read_only_fields
182
+ attr_accessor :write_only_fields
177
183
 
178
184
  # Option to use vendored assets (requires sprockets or propshaft) rather than linking to
179
185
  # external assets (the default).
@@ -187,12 +193,13 @@ module RESTFramework
187
193
 
188
194
  self.label_fields = DEFAULT_LABEL_FIELDS
189
195
  self.search_columns = DEFAULT_SEARCH_COLUMNS
190
- self.exclude_body_fields = DEFAULT_EXCLUDE_BODY_FIELDS
196
+ self.read_only_fields = DEFAULT_READ_ONLY_FIELDS
197
+ self.write_only_fields = DEFAULT_WRITE_ONLY_FIELDS
191
198
  end
192
199
  end
193
200
 
194
201
  def self.config
195
- return @config ||= Config.new
202
+ @config ||= Config.new
196
203
  end
197
204
 
198
205
  def self.configure
@@ -200,7 +207,7 @@ module RESTFramework
200
207
  end
201
208
 
202
209
  def self.features
203
- return @features ||= {}
210
+ @features ||= {}
204
211
  end
205
212
  end
206
213