rest_framework 1.0.0.beta2 → 1.0.0

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.
@@ -7,7 +7,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
7
7
  class_attribute :action_config
8
8
 
9
9
  # Accept/ignore `*args` to be compatible with the `ActiveModel::Serializer#initialize` signature.
10
- def initialize(object=nil, *args, many: nil, model: nil, **kwargs)
10
+ def initialize(object = nil, *args, many: nil, model: nil, **kwargs)
11
11
  super(object, *args, **kwargs)
12
12
 
13
13
  if many.nil?
@@ -28,7 +28,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
28
28
 
29
29
  # Get controller action, if possible.
30
30
  def get_action
31
- return @controller&.action_name&.to_sym
31
+ @controller&.action_name&.to_sym
32
32
  end
33
33
 
34
34
  # Get a locally defined native serializer configuration, if one is defined.
@@ -47,7 +47,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
47
47
  return self.singular_config if @many == false && self.singular_config
48
48
 
49
49
  # Lastly, try returning the default config, or singular/plural config in that order.
50
- return self.config || self.singular_config || self.plural_config
50
+ self.config || self.singular_config || self.plural_config
51
51
  end
52
52
 
53
53
  # Get a native serializer configuration from the controller.
@@ -60,7 +60,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
60
60
  controller_serializer = @controller.class.native_serializer_singular_config
61
61
  end
62
62
 
63
- return controller_serializer || @controller.class.native_serializer_config
63
+ controller_serializer || @controller.class.native_serializer_config
64
64
  end
65
65
 
66
66
  # Filter a single subconfig for specific keys. By default, keys from `fields` are removed from the
@@ -99,7 +99,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
99
99
  subcfg = subcfg.to_sym
100
100
 
101
101
  if add
102
- subcfg = subcfg.in?(fields) ? fields : [subcfg, *fields]
102
+ subcfg = subcfg.in?(fields) ? fields : [ subcfg, *fields ]
103
103
  elsif only
104
104
  subcfg = subcfg.in?(fields) ? subcfg : []
105
105
  else
@@ -107,7 +107,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
107
107
  end
108
108
  end
109
109
 
110
- return subcfg
110
+ subcfg
111
111
  end
112
112
 
113
113
  # Filter out configuration properties based on the :except/:only query parameters.
@@ -153,7 +153,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
153
153
  end
154
154
  end
155
155
 
156
- return cfg
156
+ cfg
157
157
  end
158
158
 
159
159
  # Get the associations limit from the controller.
@@ -174,7 +174,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
174
174
  end
175
175
  end
176
176
 
177
- return @_get_associations_limit = limit
177
+ @_get_associations_limit = limit
178
178
  end
179
179
 
180
180
  # Get a serializer configuration from the controller. `@controller` and `@model` must be set.
@@ -210,7 +210,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
210
210
  sub_methods << sf
211
211
  end
212
212
  end
213
- sub_config = {only: sub_columns, methods: sub_methods}
213
+ sub_config = { only: sub_columns, methods: sub_methods }
214
214
 
215
215
  # Apply certain rules regarding collection associations.
216
216
  if ref.collection?
@@ -257,7 +257,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
257
257
  # ActiveStorage Integration: Define attachment serializer method.
258
258
  if ref.macro == :has_one_attached
259
259
  serializer_methods[f] = f
260
- includes_map[f] = {"#{f}_attachment": :blob}
260
+ includes_map[f] = { "#{f}_attachment": :blob }
261
261
  self.define_singleton_method(f) do |record|
262
262
  attached = record.send(f)
263
263
  next attached.attachment ? {
@@ -268,7 +268,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
268
268
  end
269
269
  elsif ref.macro == :has_many_attached
270
270
  serializer_methods[f] = f
271
- includes_map[f] = {"#{f}_attachments": :blob}
271
+ includes_map[f] = { "#{f}_attachments": :blob }
272
272
  self.define_singleton_method(f) do |record|
273
273
  # Iterating the collection yields attachment objects.
274
274
  next record.send(f).map { |a|
@@ -288,7 +288,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
288
288
  end
289
289
  end
290
290
 
291
- return {
291
+ {
292
292
  only: columns,
293
293
  include: includes,
294
294
  methods: methods,
@@ -317,24 +317,24 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
317
317
  end
318
318
 
319
319
  # By default, pass an empty configuration, using the default Rails serializer.
320
- return {}
320
+ {}
321
321
  end
322
322
 
323
323
  # Get a configuration passable to `serializable_hash` for the object, filtered if required.
324
324
  def get_serializer_config
325
- return self.filter_from_request(self.get_raw_serializer_config)
325
+ self.filter_from_request(self.get_raw_serializer_config)
326
326
  end
327
327
 
328
328
  # Serialize a single record and merge results of `serializer_methods`.
329
329
  def _serialize(record, config, serializer_methods)
330
330
  # Ensure serializer_methods is either falsy, or a hash.
331
331
  if serializer_methods && !serializer_methods.is_a?(Hash)
332
- serializer_methods = [serializer_methods].flatten.map { |m| [m, m] }.to_h
332
+ serializer_methods = [ serializer_methods ].flatten.map { |m| [ m, m ] }.to_h
333
333
  end
334
334
 
335
335
  # 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,
336
+ record.serializable_hash(config).merge(
337
+ serializer_methods&.map { |m, k| [ k.to_sym, self.send(m, record) ] }.to_h,
338
338
  )
339
339
  end
340
340
 
@@ -352,29 +352,29 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
352
352
  return @object.map { |r| self._serialize(r, config, serializer_methods) }
353
353
  end
354
354
 
355
- return self._serialize(@object, config, serializer_methods)
355
+ self._serialize(@object, config, serializer_methods)
356
356
  end
357
357
 
358
358
  # Allow a serializer instance to be used as a hash directly in a nested serializer config.
359
359
  def [](key)
360
360
  @_nested_config ||= self.get_serializer_config
361
- return @_nested_config[key]
361
+ @_nested_config[key]
362
362
  end
363
363
 
364
364
  def []=(key, value)
365
365
  @_nested_config ||= self.get_serializer_config
366
- return @_nested_config[key] = value
366
+ @_nested_config[key] = value
367
367
  end
368
368
 
369
369
  # Allow a serializer class to be used as a hash directly in a nested serializer config.
370
370
  def self.[](key)
371
371
  @_nested_config ||= self.new.get_serializer_config
372
- return @_nested_config[key]
372
+ @_nested_config[key]
373
373
  end
374
374
 
375
375
  def self.[]=(key, value)
376
376
  @_nested_config ||= self.new.get_serializer_config
377
- return @_nested_config[key] = value
377
+ @_nested_config[key] = value
378
378
  end
379
379
  end
380
380
 
@@ -1,49 +1,32 @@
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
- # Convert `extra_actions` hash to a consistent format: `{path:, methods:, kwargs:}`.
5
- #
6
- # If a controller is provided, labels will be added to any metadata fields.
7
- def self.parse_extra_actions(extra_actions, controller: nil)
8
- return (extra_actions || {}).map { |k, v|
4
+ # Convert `extra_actions` hash to a consistent format: `{path:, methods:, metadata:, kwargs:}`.
5
+ def self.parse_extra_actions(extra_actions)
6
+ (extra_actions || {}).map { |k, v|
9
7
  path = k
8
+ kwargs = {}
10
9
 
11
10
  # Convert structure to path/methods/kwargs.
12
- if v.is_a?(Hash) # Allow kwargs to be used to define path differently from the key.
11
+ if v.is_a?(Hash)
13
12
  # Symbolize keys (which also makes a copy so we don't mutate the original).
14
13
  v = v.symbolize_keys
15
- methods = v.delete(:methods)
16
- if v.key?(:method)
17
- methods = v.delete(:method)
18
- end
19
14
 
20
- # Add label to metadata fields, if any exist.
21
- metadata = v[:metadata]
22
- if controller && metadata&.key?(:fields)
23
- metadata[:fields] = metadata[:fields].map { |f|
24
- [f, {}]
25
- }.to_h if metadata[:fields].is_a?(Array)
26
- metadata[:fields]&.each do |field, cfg|
27
- cfg[:label] = controller.label_for(field) unless cfg[:label]
28
- end
29
- end
15
+ # Cast method/methods to an array.
16
+ methods = [ v.delete(:methods), v.delete(:method) ].flatten.compact
30
17
 
31
18
  # Override path if it's provided.
32
19
  if v.key?(:path)
33
20
  path = v.delete(:path)
34
21
  end
35
22
 
23
+ # Extract metadata, if provided.
24
+ metadata = v.delete(:metadata).presence
25
+
36
26
  # Pass any further kwargs to the underlying Rails interface.
37
- kwargs = v.presence
38
- elsif v.is_a?(Array) && v.length == 1
39
- methods = v[0]
27
+ kwargs = v
40
28
  else
41
- methods = v
42
- end
43
-
44
- # Insert action label if it's not provided.
45
- if controller
46
- metadata[:label] ||= controller.label_for(k)
29
+ methods = [ v ].flatten
47
30
  end
48
31
 
49
32
  next [
@@ -51,6 +34,7 @@ module RESTFramework::Utils
51
34
  {
52
35
  path: path,
53
36
  methods: methods,
37
+ metadata: metadata,
54
38
  kwargs: kwargs,
55
39
  }.compact,
56
40
  ]
@@ -59,7 +43,7 @@ module RESTFramework::Utils
59
43
 
60
44
  # Get actions which should be skipped for a given controller.
61
45
  def self.get_skipped_builtin_actions(controller_class)
62
- return (
46
+ (
63
47
  RESTFramework::BUILTIN_ACTIONS.keys + RESTFramework::BUILTIN_MEMBER_ACTIONS.keys
64
48
  ).reject do |action|
65
49
  controller_class.method_defined?(action)
@@ -74,7 +58,7 @@ module RESTFramework::Utils
74
58
  # Normalize a path pattern by replacing URL params with generic placeholder, and removing the
75
59
  # `(.:format)` at the end.
76
60
  def self.comparable_path(path)
77
- return path.gsub("(.:format)", "").gsub(/:[0-9A-Za-z_-]+/, ":x")
61
+ path.gsub("(.:format)", "").gsub(/:[0-9A-Za-z_-]+/, ":x")
78
62
  end
79
63
 
80
64
  # Show routes under a controller action; used for the browsable API.
@@ -90,7 +74,7 @@ module RESTFramework::Utils
90
74
 
91
75
  # Return routes that match our current route subdomain/pattern, grouped by controller. We
92
76
  # precompute certain properties of the route for performance.
93
- return application_routes.routes.select { |r|
77
+ application_routes.routes.select { |r|
94
78
  # We `select` first to avoid unnecessarily calculating metadata for routes we don't even want
95
79
  # to show.
96
80
  (r.defaults[:subdomain].blank? || r.defaults[:subdomain] == request.subdomain) &&
@@ -116,7 +100,7 @@ module RESTFramework::Utils
116
100
  verb: r.verb,
117
101
  path: path,
118
102
  path_with_params: r.format(
119
- r.required_parts.each_with_index.map { |p, i| [p, path_params[i]] }.to_h,
103
+ r.required_parts.each_with_index.map { |p, i| [ p, path_params[i] ] }.to_h,
120
104
  ),
121
105
  relative_path: relative_path,
122
106
  concat_path: concat_path,
@@ -139,17 +123,19 @@ module RESTFramework::Utils
139
123
  ]
140
124
  }.group_by { |r| r[:controller] }.sort_by { |c, _r|
141
125
  # Sort the controller groups by current controller first, then alphanumerically.
142
- [request.params[:controller] == c ? 0 : 1, c]
126
+ # Note: Use `controller_path` instead of `params[:controller]` to avoid re-raising a
127
+ # `ActionDispatch::Http::Parameters::ParseError` exception.
128
+ [ request.controller_class.controller_path == c ? 0 : 1, c ]
143
129
  }.to_h
144
130
  end
145
131
 
146
132
  # Custom inflector for RESTful controllers.
147
- def self.inflect(s, acronyms=nil)
133
+ def self.inflect(s, acronyms = nil)
148
134
  acronyms&.each do |acronym|
149
135
  s = s.gsub(/\b#{acronym}\b/i, acronym)
150
136
  end
151
137
 
152
- return s
138
+ s
153
139
  end
154
140
 
155
141
  # Parse fields hashes.
@@ -167,12 +153,12 @@ module RESTFramework::Utils
167
153
  parsed_fields -= h[:except].map(&:to_s) if h[:except]
168
154
 
169
155
  # Warn for any unknown keys.
170
- (h.keys - [:only, :except, :include, :exclude]).each do |k|
156
+ (h.keys - [ :only, :except, :include, :exclude ]).each do |k|
171
157
  Rails.logger.warn("RRF: Unknown key in fields hash: #{k}")
172
158
  end
173
159
 
174
160
  # We should always return strings, not symbols.
175
- return parsed_fields.map(&:to_s)
161
+ parsed_fields.map(&:to_s)
176
162
  end
177
163
 
178
164
  # Get the fields for a given model, including not just columns (which includes
@@ -195,7 +181,7 @@ module RESTFramework::Utils
195
181
  # Associations:
196
182
  associations = model.reflections.map { |association, ref|
197
183
  # Ignore associations for which we have custom integrations.
198
- if ref.class_name.in?(%w(ActionText::RichText ActiveStorage::Attachment ActiveStorage::Blob))
184
+ if ref.class_name.in?(%w[ActionText::RichText ActiveStorage::Attachment ActiveStorage::Blob])
199
185
  next nil
200
186
  end
201
187
 
@@ -208,29 +194,29 @@ module RESTFramework::Utils
208
194
  next association
209
195
  }.compact
210
196
 
211
- return base_fields + associations + atf + asf
197
+ base_fields + associations + atf + asf
212
198
  end
213
199
 
214
200
  # Get the sub-fields that may be serialized and filtered/ordered for a reflection.
215
201
  def self.sub_fields_for(ref)
216
202
  if !ref.polymorphic? && model = ref.klass
217
- sub_fields = [model.primary_key].flatten.compact
203
+ sub_fields = [ model.primary_key ].flatten.compact
218
204
  label_fields = RESTFramework.config.label_fields
219
205
 
220
206
  # Preferrably find a database column to use as label.
221
207
  if match = label_fields.find { |f| f.in?(model.column_names) }
222
- return sub_fields + [match]
208
+ return sub_fields + [ match ]
223
209
  end
224
210
 
225
211
  # Otherwise, find a method.
226
212
  if match = label_fields.find { |f| model.method_defined?(f) }
227
- return sub_fields + [match]
213
+ return sub_fields + [ match ]
228
214
  end
229
215
 
230
216
  return sub_fields
231
217
  end
232
218
 
233
- return ["id", "name"]
219
+ [ "id", "name" ]
234
220
  end
235
221
 
236
222
  # Get a field's id/ids variation.
@@ -243,7 +229,7 @@ module RESTFramework::Utils
243
229
  return reflection.foreign_key
244
230
  end
245
231
 
246
- return nil
232
+ nil
247
233
  end
248
234
 
249
235
  # Wrap a serializer with an adapter if it is an ActiveModel::Serializer.
@@ -252,6 +238,6 @@ module RESTFramework::Utils
252
238
  return RESTFramework::ActiveModelSerializerAdapterFactory.for(s)
253
239
  end
254
240
 
255
- return s
241
+ s
256
242
  end
257
243
  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
@@ -9,17 +9,21 @@ module RESTFramework
9
9
  BUILTIN_MEMBER_ACTIONS = {
10
10
  show: :get,
11
11
  edit: :get,
12
- update: [:put, :patch].freeze,
12
+ update: [ :put, :patch ].freeze,
13
13
  destroy: :delete,
14
14
  }.freeze
15
15
  RRF_BUILTIN_ACTIONS = {
16
16
  options: :options,
17
17
  }.freeze
18
18
  RRF_BUILTIN_BULK_ACTIONS = {
19
- update_all: [:put, :patch].freeze,
19
+ update_all: [ :put, :patch ].freeze,
20
20
  destroy_all: :delete,
21
21
  }.freeze
22
22
 
23
+ # Storage for extra routes and associated metadata.
24
+ EXTRA_ACTION_ROUTES = Set.new
25
+ ROUTE_METADATA = {}
26
+
23
27
  # We put most vendored external assets into these files to make precompilation and serving faster.
24
28
  EXTERNAL_CSS_NAME = "rest_framework/external.min.css"
25
29
  EXTERNAL_JS_NAME = "rest_framework/external.min.js"
@@ -61,12 +65,12 @@ module RESTFramework
61
65
  "highlight-a11y-dark.min.css" => {
62
66
  url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/styles/a11y-dark.min.css",
63
67
  sri: "sha512-Vj6gPCk8EZlqnoveEyuGyYaWZ1+jyjMPg8g4shwyyNlRQl6d3L9At02ZHQr5K6s5duZl/+YKMnM3/8pDhoUphg==",
64
- extra_tag_attrs: {class: "rrf-dark-mode"},
68
+ extra_tag_attrs: { class: "rrf-dark-mode" },
65
69
  },
66
70
  "highlight-a11y-light.min.css" => {
67
71
  url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/styles/a11y-light.min.css",
68
72
  sri: "sha512-WDk6RzwygsN9KecRHAfm9HTN87LQjqdygDmkHSJxVkVI7ErCZ8ZWxP6T8RvBujY1n2/E4Ac+bn2ChXnp5rnnHA==",
69
- extra_tag_attrs: {class: "rrf-light-mode"},
73
+ extra_tag_attrs: { class: "rrf-light-mode" },
70
74
  },
71
75
 
72
76
  # NeatJSON
@@ -113,7 +117,7 @@ module RESTFramework
113
117
  raise "Unknown asset extension: #{ext}."
114
118
  end
115
119
 
116
- [name, cfg]
120
+ [ name, cfg ]
117
121
  }.to_h.freeze
118
122
  # rubocop:enable Layout/LineLength
119
123
 
@@ -122,8 +126,8 @@ module RESTFramework
122
126
  # Global configuration should be kept minimal, as controller-level configurations allows multiple
123
127
  # APIs to be defined to behave differently.
124
128
  class Config
125
- DEFAULT_LABEL_FIELDS = %w(name label login title email username url).freeze
126
- DEFAULT_SEARCH_COLUMNS = DEFAULT_LABEL_FIELDS + %w(description note).freeze
129
+ DEFAULT_LABEL_FIELDS = %w[name label login title email username url].freeze
130
+ DEFAULT_SEARCH_COLUMNS = DEFAULT_LABEL_FIELDS + %w[description note].freeze
127
131
  DEFAULT_EXCLUDE_BODY_FIELDS = %w[
128
132
  created_at
129
133
  created_by
@@ -136,15 +140,18 @@ module RESTFramework
136
140
  authenticity_token
137
141
  ].freeze
138
142
 
139
- # Do not run `rrf_finalize` on controllers automatically using a `TracePoint` hook. This is a
140
- # performance option and must be global because we have to determine this before any
141
- # controller-specific configuration is set. If this is set to `true`, then you must manually
142
- # call `rrf_finalize` after any configuration on each controller that needs to participate
143
- # in:
143
+ # Permits use of `render(api: obj)` syntax over `render_api(obj)`; `true` by default.
144
+ attr_accessor :register_api_renderer
145
+
146
+ # Run `rrf_finalize` on controllers automatically using a `TracePoint` hook. This is `true` by
147
+ # default, and can be disabled for performance, and must be global because we have to determine
148
+ # this before any controller-specific configuration is set. If this is set to `false`, then you
149
+ # must manually call `rrf_finalize` after any configuration on each controller that needs to
150
+ # participate in:
144
151
  # - Model delegation, for the helper methods to be defined dynamically.
145
152
  # - Websockets, for `::Channel` class to be defined dynamically.
146
153
  # - Controller configuration freezing.
147
- attr_accessor :disable_auto_finalize
154
+ attr_accessor :auto_finalize
148
155
 
149
156
  # Freeze configuration attributes during finalization to prevent accidental mutation.
150
157
  attr_accessor :freeze_config
@@ -173,7 +180,11 @@ module RESTFramework
173
180
  attr_accessor :use_vendored_assets
174
181
 
175
182
  def initialize
183
+ self.register_api_renderer = true
184
+ self.auto_finalize = true
185
+
176
186
  self.show_backtrace = Rails.env.development?
187
+
177
188
  self.label_fields = DEFAULT_LABEL_FIELDS
178
189
  self.search_columns = DEFAULT_SEARCH_COLUMNS
179
190
  self.exclude_body_fields = DEFAULT_EXCLUDE_BODY_FIELDS
@@ -181,7 +192,7 @@ module RESTFramework
181
192
  end
182
193
 
183
194
  def self.config
184
- return @config ||= Config.new
195
+ @config ||= Config.new
185
196
  end
186
197
 
187
198
  def self.configure
@@ -189,7 +200,7 @@ module RESTFramework
189
200
  end
190
201
 
191
202
  def self.features
192
- return @features ||= {}
203
+ @features ||= {}
193
204
  end
194
205
  end
195
206
 
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: 1.0.0.beta2
4
+ version: 1.0.0
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: 2024-12-26 00:00:00.000000000 Z
11
+ date: 2025-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -96,12 +96,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
96
96
  requirements:
97
97
  - - ">="
98
98
  - !ruby/object:Gem::Version
99
- version: 2.3.0
99
+ version: 2.7.5
100
100
  required_rubygems_version: !ruby/object:Gem::Requirement
101
101
  requirements:
102
- - - ">"
102
+ - - ">="
103
103
  - !ruby/object:Gem::Version
104
- version: 1.3.1
104
+ version: '0'
105
105
  requirements: []
106
106
  rubygems_version: 3.4.10
107
107
  signing_key: