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.
@@ -5,13 +5,13 @@ module RESTFramework::Mixins::BaseModelControllerMixin
5
5
  return value unless BASE64_REGEX.match?(value)
6
6
 
7
7
  _, content_type, payload = value.match(BASE64_REGEX).to_a
8
- return {
8
+ {
9
9
  io: StringIO.new(Base64.decode64(payload)),
10
10
  content_type: content_type,
11
11
  filename: "file_#{field}#{Rack::Mime::MIME_TYPES.invert[content_type]}",
12
12
  }
13
13
  }
14
- ACTIVESTORAGE_KEYS = [:io, :content_type, :filename, :identify, :key]
14
+ ACTIVESTORAGE_KEYS = [ :io, :content_type, :filename, :identify, :key ]
15
15
 
16
16
  include RESTFramework::BaseControllerMixin
17
17
 
@@ -74,7 +74,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
74
74
  }
75
75
 
76
76
  module ClassMethods
77
- IGNORE_VALIDATORS_WITH_KEYS = [:if, :unless].freeze
77
+ IGNORE_VALIDATORS_WITH_KEYS = [ :if, :unless ].freeze
78
78
 
79
79
  def get_model
80
80
  return @model if @model
@@ -91,7 +91,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
91
91
 
92
92
  # Override to include ActiveRecord i18n-translated column names.
93
93
  def label_for(s)
94
- return self.get_model.human_attribute_name(s, default: super)
94
+ self.get_model.human_attribute_name(s, default: super)
95
95
  end
96
96
 
97
97
  # Get the available fields. Fallback to this controller's model columns, or an empty array. This
@@ -121,7 +121,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
121
121
  input_fields = input_fields.map(&:to_s)
122
122
  end
123
123
 
124
- return input_fields
124
+ input_fields
125
125
  end
126
126
 
127
127
  # Get a full field configuration, including defaults and inferred values.
@@ -141,7 +141,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
141
141
  .select { |n| n.to_s.start_with?("rich_text_") }
142
142
  attachment_reflections = model.attachment_reflections
143
143
 
144
- return @field_configuration = self.get_fields.map { |f|
144
+ @field_configuration = self.get_fields.map { |f|
145
145
  cfg = field_config[f]&.dup || {}
146
146
  cfg[:label] ||= self.label_for(f)
147
147
 
@@ -218,7 +218,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
218
218
  v[:kind] = "method"
219
219
  end
220
220
 
221
- next [sf, v]
221
+ next [ sf, v ]
222
222
  }.to_h.compact.presence
223
223
 
224
224
  # Determine if we render id/ids fields. Unfortunately, `has_one` does not provide this
@@ -231,7 +231,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
231
231
  if self.permit_nested_attributes_assignment && (
232
232
  nested_opts = model.nested_attributes_options[f.to_sym].presence
233
233
  )
234
- cfg[:nested_attributes_options] = {field: "#{f}_attributes", **nested_opts}
234
+ cfg[:nested_attributes_options] = { field: "#{f}_attributes", **nested_opts }
235
235
  end
236
236
 
237
237
  begin
@@ -283,7 +283,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
283
283
  cfg[:validators][kind] << options
284
284
  end
285
285
 
286
- next [f, cfg]
286
+ next [ f, cfg ]
287
287
  }.to_h.with_indifferent_access
288
288
  end
289
289
 
@@ -295,7 +295,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
295
295
  required: field_configuration.select { |_, cfg| cfg[:required] }.keys,
296
296
  type: "object",
297
297
  properties: field_configuration.map { |f, cfg|
298
- v = {title: cfg[:label]}
298
+ v = { title: cfg[:label] }
299
299
 
300
300
  if cfg[:kind] == "association"
301
301
  v[:type] = cfg[:reflection].collection? ? "array" : "object"
@@ -324,7 +324,14 @@ module RESTFramework::Mixins::BaseModelControllerMixin
324
324
  v[:"x-rrf-kind"] = cfg[:kind] if cfg[:kind]
325
325
 
326
326
  if cfg[:reflection]
327
- v[:"x-rrf-reflection"] = cfg[:reflection]
327
+ v[:"x-rrf-reflection"] = {
328
+ class_name: cfg[:reflection].class_name,
329
+ foreign_key: cfg[:reflection].foreign_key,
330
+ association_foreign_key: cfg[:reflection].association_foreign_key,
331
+ association_primary_key: cfg[:reflection].association_primary_key,
332
+ inverse_of: cfg[:reflection].inverse_of&.name,
333
+ join_table: cfg[:reflection].join_table,
334
+ }.compact
328
335
  v[:"x-rrf-association_pk"] = cfg[:association_pk]
329
336
  v[:"x-rrf-sub_fields"] = cfg[:sub_fields]
330
337
  v[:"x-rrf-sub_fields_metadata"] = cfg[:sub_fields_metadata]
@@ -332,39 +339,71 @@ module RESTFramework::Mixins::BaseModelControllerMixin
332
339
  v[:"x-rrf-nested_attributes_options"] = cfg[:nested_attributes_options]
333
340
  end
334
341
 
335
- next [f, v]
342
+ next [ f, v ]
336
343
  }.to_h,
337
344
  }
338
345
 
339
- return @openapi_schema
346
+ @openapi_schema
340
347
  end
341
348
 
342
- def openapi_document(request, route_group_name, routes)
343
- document = super
344
- schema_name = routes[0][:controller].camelize.gsub("::", ".")
349
+ def openapi_schema_name
350
+ @openapi_schema_name ||= self.name.chomp("Controller").gsub("::", ".")
351
+ end
345
352
 
346
- # Insert schema into the document.
347
- document[:components] ||= {}
348
- document[:components][:schemas] ||= {}
349
- document[:components][:schemas][schema_name] = self.openapi_schema
353
+ def openapi_paths(_routes, tag)
354
+ paths = super
355
+ schema_name = self.openapi_schema_name
350
356
 
351
- # Reference schema for specific actions with a `requestBody`.
352
- document[:paths].each do |_path, actions|
353
- actions.each do |_method, action|
357
+ # Reference the model schema for request body and successful default responses.
358
+ paths.each do |_path, actions|
359
+ actions.each do |method, action|
354
360
  next unless action.is_a?(Hash)
355
361
 
356
- injectables = [action.dig(:requestBody, :content), *action[:responses].values.map { |r|
357
- r[:content]
358
- }].compact
359
- injectables.each do |i|
360
- i.each do |_, v|
361
- v[:schema] = {"$ref" => "#/components/schemas/#{schema_name}"}
362
+ extra_action = action.dig("x-rrf-metadata", :extra_action)
363
+
364
+ # Adjustments for builtin actions:
365
+ if !extra_action && method != "options" # rubocop:disable Style/Next
366
+ # Add schema to request body content types.
367
+ action.dig(:requestBody, :content)&.each do |_t, v|
368
+ v[:schema] = { "$ref" => "#/components/schemas/#{schema_name}" }
369
+ end
370
+
371
+ # Add schema to successful response body content types.
372
+ action[:responses].each do |status, response|
373
+ next unless status.to_s.start_with?("2")
374
+
375
+ response[:content]&.each do |t, v|
376
+ next if t == "text/html"
377
+
378
+ v[:schema] = { "$ref" => "#/components/schemas/#{schema_name}" }
379
+ end
380
+ end
381
+
382
+ # Translate 200->201 for the create action.
383
+ if action[:summary] == "Create"
384
+ action[:responses][201] = action[:responses].delete(200)
385
+ end
386
+
387
+ # Translate 200->204 for the destroy action.
388
+ if action[:summary] == "Destroy"
389
+ action[:responses][204] = action[:responses].delete(200)
362
390
  end
363
391
  end
364
392
  end
365
393
  end
366
394
 
367
- return document.merge(
395
+ paths
396
+ end
397
+
398
+ def openapi_document(request, route_group_name, _routes)
399
+ document = super
400
+
401
+ # Insert schema into the document.
402
+ document[:components] ||= {}
403
+ document[:components][:schemas] ||= {}
404
+ document[:components][:schemas][self.openapi_schema_name] = self.openapi_schema
405
+
406
+ document.merge(
368
407
  {
369
408
  "x-rrf-primary_key" => self.get_model.primary_key,
370
409
  "x-rrf-callbacks" => self._process_action_callbacks.as_json,
@@ -382,9 +421,9 @@ module RESTFramework::Mixins::BaseModelControllerMixin
382
421
  model = self.class.get_model
383
422
 
384
423
  if model.method(action).parameters.last&.first == :keyrest
385
- render_api(model.send(action, **params))
424
+ render(api: model.send(action, **params))
386
425
  else
387
- render_api(model.send(action))
426
+ render(api: model.send(action))
388
427
  end
389
428
  end
390
429
  end
@@ -398,9 +437,9 @@ module RESTFramework::Mixins::BaseModelControllerMixin
398
437
  record = self.get_record
399
438
 
400
439
  if record.method(action).parameters.last&.first == :keyrest
401
- render_api(record.send(action, **params))
440
+ render(api: record.send(action, **params))
402
441
  else
403
- render_api(record.send(action))
442
+ render(api: record.send(action))
404
443
  end
405
444
  end
406
445
  end
@@ -439,7 +478,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
439
478
  end
440
479
 
441
480
  def get_fields
442
- return self.class.get_fields(input_fields: self.class.fields)
481
+ self.class.get_fields(input_fields: self.class.fields)
443
482
  end
444
483
 
445
484
  # Get a hash of strong parameters for the current action.
@@ -488,7 +527,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
488
527
  # TODO: Consider adjusting this based on `nested_attributes_options`.
489
528
  if self.class.permit_nested_attributes_assignment
490
529
  hash_variations["#{f}_attributes"] = (
491
- config[:sub_fields] + ["_destroy"]
530
+ config[:sub_fields] + [ "_destroy" ]
492
531
  )
493
532
  end
494
533
 
@@ -502,11 +541,11 @@ module RESTFramework::Mixins::BaseModelControllerMixin
502
541
  @_get_allowed_parameters += variations
503
542
  @_get_allowed_parameters << hash_variations
504
543
 
505
- return @_get_allowed_parameters
544
+ @_get_allowed_parameters
506
545
  end
507
546
 
508
547
  def get_serializer_class
509
- return super || RESTFramework::NativeSerializer
548
+ super || RESTFramework::NativeSerializer
510
549
  end
511
550
 
512
551
  # Use strong parameters to filter the request body.
@@ -578,8 +617,8 @@ module RESTFramework::Mixins::BaseModelControllerMixin
578
617
  body_params = if allowed_params == true
579
618
  ActionController::Parameters.new(data).permit!
580
619
  elsif bulk_mode
581
- pk = bulk_mode == :update ? [pk] : []
582
- ActionController::Parameters.new(data).permit({_json: allowed_params + pk})
620
+ pk = bulk_mode == :update ? [ pk ] : []
621
+ ActionController::Parameters.new(data).permit({ _json: allowed_params + pk })
583
622
  else
584
623
  ActionController::Parameters.new(data).permit(*allowed_params)
585
624
  end
@@ -601,7 +640,7 @@ module RESTFramework::Mixins::BaseModelControllerMixin
601
640
  # Filter fields in `exclude_body_fields`.
602
641
  (self.class.exclude_body_fields || []).each { |f| body_params.delete(f) }
603
642
 
604
- return body_params
643
+ body_params
605
644
  end
606
645
  alias_method :get_create_params, :get_body_params
607
646
  alias_method :get_update_params, :get_body_params
@@ -615,14 +654,14 @@ module RESTFramework::Mixins::BaseModelControllerMixin
615
654
  return model.all
616
655
  end
617
656
 
618
- return nil
657
+ nil
619
658
  end
620
659
 
621
660
  # Filter the recordset and return records this request has access to.
622
661
  def get_records
623
662
  data = self.get_recordset
624
663
 
625
- return @records ||= self.class.filter_backends&.reduce(data) { |d, filter|
664
+ @records ||= self.class.filter_backends&.reduce(data) { |d, filter|
626
665
  filter.new(controller: self).filter_data(d)
627
666
  } || data
628
667
  end
@@ -655,9 +694,9 @@ module RESTFramework::Mixins::BaseModelControllerMixin
655
694
 
656
695
  # Return the record. Route key is always `:id` by Rails' convention.
657
696
  if is_pk
658
- return @record = collection.find(request.path_parameters[:id])
697
+ @record = collection.find(request.path_parameters[:id])
659
698
  else
660
- return @record = collection.find_by!(find_by_key => request.path_parameters[:id])
699
+ @record = collection.find_by!(find_by_key => request.path_parameters[:id])
661
700
  end
662
701
  end
663
702
 
@@ -681,8 +720,8 @@ module RESTFramework::Mixins::BaseModelControllerMixin
681
720
  # the serializer directly. This would fail for active model serializers, but maybe we don't
682
721
  # care?
683
722
  s = RESTFramework::Utils.wrap_ams(self.get_serializer_class)
684
- return records.map do |record|
685
- s.new(record, controller: self).serialize.merge!({errors: record.errors.presence}.compact)
723
+ records.map do |record|
724
+ s.new(record, controller: self).serialize.merge!({ errors: record.errors.presence }.compact)
686
725
  end
687
726
  end
688
727
  end
@@ -690,7 +729,7 @@ end
690
729
  # Mixin for listing records.
691
730
  module RESTFramework::Mixins::ListModelMixin
692
731
  def index
693
- render_api(self.get_index_records)
732
+ render(api: self.get_index_records)
694
733
  end
695
734
 
696
735
  # Get records with both filtering and pagination applied.
@@ -711,40 +750,40 @@ module RESTFramework::Mixins::ListModelMixin
711
750
  end
712
751
  end
713
752
 
714
- return records
753
+ records
715
754
  end
716
755
  end
717
756
 
718
757
  # Mixin for showing records.
719
758
  module RESTFramework::Mixins::ShowModelMixin
720
759
  def show
721
- render_api(self.get_record)
760
+ render(api: self.get_record)
722
761
  end
723
762
  end
724
763
 
725
764
  # Mixin for creating records.
726
765
  module RESTFramework::Mixins::CreateModelMixin
727
766
  def create
728
- render_api(self.create!, status: :created)
767
+ render(api: self.create!, status: :created)
729
768
  end
730
769
 
731
770
  # Perform the `create!` call and return the created record.
732
771
  def create!
733
- return self.get_create_from.create!(self.get_create_params)
772
+ self.get_create_from.create!(self.get_create_params)
734
773
  end
735
774
  end
736
775
 
737
776
  # Mixin for updating records.
738
777
  module RESTFramework::Mixins::UpdateModelMixin
739
778
  def update
740
- render_api(self.update!)
779
+ render(api: self.update!)
741
780
  end
742
781
 
743
782
  # Perform the `update!` call and return the updated record.
744
783
  def update!
745
784
  record = self.get_record
746
785
  record.update!(self.get_update_params)
747
- return record
786
+ record
748
787
  end
749
788
  end
750
789
 
@@ -752,12 +791,12 @@ end
752
791
  module RESTFramework::Mixins::DestroyModelMixin
753
792
  def destroy
754
793
  self.destroy!
755
- render_api("")
794
+ render(api: "")
756
795
  end
757
796
 
758
797
  # Perform the `destroy!` call and return the destroyed (and frozen) record.
759
798
  def destroy!
760
- return self.get_record.destroy!
799
+ self.get_record.destroy!
761
800
  end
762
801
  end
763
802
 
@@ -34,11 +34,11 @@ class RESTFramework::Paginators::PageNumberPaginator < RESTFramework::Paginators
34
34
  end
35
35
 
36
36
  # Ensure we return at least 1.
37
- return [page_size, 1].max
37
+ [ page_size, 1 ].max
38
38
  end
39
39
 
40
40
  # Get the page and return it so the caller can serialize it.
41
- def get_page(page_number=nil)
41
+ def get_page(page_number = nil)
42
42
  # If page number isn't provided, infer from the params or use 1 as a fallback value.
43
43
  unless page_number
44
44
  page_number = @controller&.params&.[](@controller.class.page_query_param&.to_sym)
@@ -55,7 +55,7 @@ class RESTFramework::Paginators::PageNumberPaginator < RESTFramework::Paginators
55
55
 
56
56
  # Get the data page and return it so the caller can serialize the data in the proper format.
57
57
  page_index = @page_number - 1
58
- return @data.limit(@page_size).offset(page_index * @page_size)
58
+ @data.limit(@page_size).offset(page_index * @page_size)
59
59
  end
60
60
 
61
61
  # Wrap the serialized page with appropriate metadata.
@@ -63,13 +63,13 @@ class RESTFramework::Paginators::PageNumberPaginator < RESTFramework::Paginators
63
63
  page_query_param = @controller.class.page_query_param
64
64
  base_params = @controller.params.to_unsafe_h
65
65
  next_url = if @page_number < @total_pages
66
- @controller.url_for({**base_params, page_query_param => @page_number + 1})
66
+ @controller.url_for({ **base_params, page_query_param => @page_number + 1 })
67
67
  end
68
68
  previous_url = if @page_number > 1
69
- @controller.url_for({**base_params, page_query_param => @page_number - 1})
69
+ @controller.url_for({ **base_params, page_query_param => @page_number - 1 })
70
70
  end
71
71
 
72
- return {
72
+ {
73
73
  count: @count,
74
74
  page: @page_number,
75
75
  page_size: @page_size,
@@ -43,7 +43,7 @@ module ActionDispatch::Routing
43
43
  end
44
44
  end
45
45
 
46
- return controller
46
+ controller
47
47
  end
48
48
 
49
49
  # Interal interface for routing extra actions.
@@ -51,9 +51,16 @@ module ActionDispatch::Routing
51
51
  parsed_actions = RESTFramework::Utils.parse_extra_actions(actions)
52
52
 
53
53
  parsed_actions.each do |action, config|
54
- [config[:methods]].flatten.each do |m|
55
- public_send(m, config[:path], action: action, **(config[:kwargs] || {}))
54
+ config[:methods].each do |m|
55
+ public_send(m, config[:path], action: action, **config[:kwargs])
56
56
  end
57
+
58
+ # Record that this route is an extra action and any metadata associated with it.
59
+ metadata = config[:metadata]
60
+ key = "#{@scope[:path]}/#{config[:path]}"
61
+ RESTFramework::EXTRA_ACTION_ROUTES.add(key)
62
+ RESTFramework::ROUTE_METADATA[key] = metadata if metadata
63
+
57
64
  yield if block_given?
58
65
  end
59
66
  end
@@ -107,7 +114,7 @@ module ActionDispatch::Routing
107
114
  RESTFramework::RRF_BUILTIN_ACTIONS.each do |action, methods|
108
115
  next unless controller_class.method_defined?(action)
109
116
 
110
- [methods].flatten.each do |m|
117
+ [ methods ].flatten.each do |m|
111
118
  public_send(m, "", action: action) if self.respond_to?(m)
112
119
  end
113
120
  end
@@ -116,7 +123,7 @@ module ActionDispatch::Routing
116
123
  RESTFramework::RRF_BUILTIN_BULK_ACTIONS.each do |action, methods|
117
124
  next unless controller_class.method_defined?(action)
118
125
 
119
- [methods].flatten.each do |m|
126
+ [ methods ].flatten.each do |m|
120
127
  public_send(m, "", action: action) if self.respond_to?(m)
121
128
  end
122
129
  end
@@ -147,7 +154,7 @@ module ActionDispatch::Routing
147
154
  end
148
155
 
149
156
  # Route a controller without the default resourceful paths.
150
- def rest_route(name=nil, **kwargs, &block)
157
+ def rest_route(name = nil, **kwargs, &block)
151
158
  controller = kwargs.delete(:controller) || name
152
159
  route_root_to = kwargs.delete(:route_root_to)
153
160
  if controller.is_a?(Class)
@@ -177,7 +184,7 @@ module ActionDispatch::Routing
177
184
  RESTFramework::RRF_BUILTIN_ACTIONS.each do |action, methods|
178
185
  next unless controller_class.method_defined?(action)
179
186
 
180
- [methods].flatten.each do |m|
187
+ [ methods ].flatten.each do |m|
181
188
  public_send(m, "", action: action) if self.respond_to?(m)
182
189
  end
183
190
  end
@@ -194,7 +201,7 @@ module ActionDispatch::Routing
194
201
  end
195
202
 
196
203
  # Route a controller's `#root` to '/' in the current scope/namespace, along with other actions.
197
- def rest_root(name=nil, **kwargs, &block)
204
+ def rest_root(name = nil, **kwargs, &block)
198
205
  # By default, use RootController#root.
199
206
  root_action = kwargs.delete(:action) || :root
200
207
  controller = kwargs.delete(:controller) || name || :root
@@ -204,7 +211,7 @@ module ActionDispatch::Routing
204
211
  kwargs[:path] = ""
205
212
  end
206
213
 
207
- return rest_route(controller, route_root_to: root_action, **kwargs) do
214
+ rest_route(controller, route_root_to: root_action, **kwargs) do
208
215
  yield if block_given?
209
216
  end
210
217
  end
@@ -1,18 +1,20 @@
1
1
  # This is a helper factory to wrap an ActiveModelSerializer to provide a `serialize` method which
2
2
  # accepts both collections and individual records. Use `.for` to build adapters.
3
+ # :nocov:
3
4
  class RESTFramework::Serializers::ActiveModelSerializerAdapterFactory
4
5
  def self.for(active_model_serializer)
5
- return Class.new(active_model_serializer) do
6
+ Class.new(active_model_serializer) do
6
7
  def serialize
7
8
  if self.object.respond_to?(:to_ary)
8
9
  return self.object.map { |r| self.class.superclass.new(r).serializable_hash }
9
10
  end
10
11
 
11
- return self.serializable_hash
12
+ self.serializable_hash
12
13
  end
13
14
  end
14
15
  end
15
16
  end
17
+ # :nocov:
16
18
 
17
19
  # Alias for convenience.
18
20
  # rubocop:disable Layout/LineLength
@@ -4,7 +4,7 @@ class RESTFramework::Serializers::BaseSerializer
4
4
  attr_accessor :object
5
5
 
6
6
  # Accept/ignore `*args` to be compatible with the `ActiveModel::Serializer#initialize` signature.
7
- def initialize(object=nil, *args, controller: nil, **kwargs)
7
+ def initialize(object = nil, *args, controller: nil, **kwargs)
8
8
  @object = object
9
9
  @controller = controller
10
10
  end
@@ -16,24 +16,26 @@ class RESTFramework::Serializers::BaseSerializer
16
16
  end
17
17
 
18
18
  # Synonym for `serialize` for compatibility with `active_model_serializers`.
19
+ # :nocov:
19
20
  def serializable_hash(*args)
20
- return self.serialize(*args)
21
+ self.serialize(*args)
21
22
  end
22
23
 
23
24
  # For compatibility with `active_model_serializers`.
24
25
  def self.cache_enabled?
25
- return false
26
+ false
26
27
  end
27
28
 
28
29
  # For compatibility with `active_model_serializers`.
29
30
  def self.fragment_cache_enabled?
30
- return false
31
+ false
31
32
  end
32
33
 
33
34
  # For compatibility with `active_model_serializers`.
34
35
  def associations(*args, **kwargs)
35
- return []
36
+ []
36
37
  end
38
+ # :nocov:
37
39
  end
38
40
 
39
41
  # Alias for convenience.