rest_framework 0.7.4 → 0.7.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e37d7baf0b2e805e4df451a8ef646e57ed078a7edd3f9d03b524198eef9fc83f
4
- data.tar.gz: c6f5bf3f8f7bfa79566a1292f7634f94b29a09a934a0179adea6e3320291cb9e
3
+ metadata.gz: af670b7b95c56b3afafeb8947b732be7e5ff3adc9f26b7bdd8f3dc6705c48094
4
+ data.tar.gz: ba4de3b422fcae2451bbb5ea5e979acaaaedfba53773ddf3a77f16c97fd4db85
5
5
  SHA512:
6
- metadata.gz: 13b7905e307bbc0f204d2570c05298cf9c4bcc025719210c3f496bac64a387f611ec4ab3772f8c268cc41b608973b7d3502cf5542d9fee0cce5587ef80a773cc
7
- data.tar.gz: 49ef1e4a637fa2f3dee50f40042cdf50a28f68f632a0e5587a339e492954ef90c1f7cd8d80f4b0fb39668838ee020556b29dfd806377e82541b3c4a8940d6b55
6
+ metadata.gz: 1f98df76846047b3435c6caaf071ebd4ac60a9ddd9da517b621d21d9e84498a28d5387e2338128c4b7815d36f0750e6c06d84147409a9ce3982a329d626a9c60
7
+ data.tar.gz: 3aa62deb130160763d08b1487496973bdc587968c278cf26be0390d9e3464d4c408ea9f30d01d8fb38702a93e311df26a81b54f3b5adf9a49b344667af5d7508
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.4
1
+ 0.7.5
@@ -3,8 +3,20 @@
3
3
  <%= csrf_meta_tags %>
4
4
  <%= csp_meta_tag rescue nil %>
5
5
 
6
+ <!-- Bootstrap -->
6
7
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
7
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.2.0/styles/vs.min.css" integrity="sha512-aWjgJTbdG4imzxTxistV5TVNffcYGtIQQm2NBNahV6LmX14Xq9WwZTL1wPjaSglUuVzYgwrq+0EuI4+vKvQHHw==" crossorigin="anonymous">
8
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
9
+
10
+ <!-- Highlight.js -->
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/vs.min.css" integrity="sha512-AVoZ71dJLtHRlsgWwujPT1hk2zxtFWsPlpTPCc/1g0WgpbmlzkqlDFduAvnOV4JJWKUquPc1ZyMc5eq4fRnKOQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
12
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js" integrity="sha512-bgHRAiTjGrzHzLyKOnpFvaEpGzJet3z4tZnXGjpsCcqOnAH6VGUx9frc5bcIhKTVLEiCO6vEhNAgx5jtLUYrfA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
13
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/json.min.js" integrity="sha512-0xYvyncS9OLE7GOpNBZFnwyh9+bq4HVgk4yVVYI678xRvE22ASicF1v6fZ1UiST+M6pn17MzFZdvVCI3jTHSyw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
14
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/xml.min.js" integrity="sha512-5zBcw+OKRkaNyvUEPlTSfYylVzgpi7KpncY36b0gRudfxIYIH0q0kl2j26uCUB3YBRM6ytQQEZSgRg+ZlBTmdA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
15
+
16
+ <!-- NeatJSON -->
17
+ <script src="https://cdn.jsdelivr.net/npm/neatjson@0.10.5/javascript/neatjson.min.js"></script>
18
+
19
+ <!-- Custom Style -->
8
20
  <style>
9
21
  /* Adjust headers to always take up their entire row, and tweak the sizing. */
10
22
  h1,h2,h3,h4,h5,h6 { display: inline-block; font-weight: normal; margin-bottom: 0; }
@@ -17,7 +29,7 @@ h6 { font-size: 1rem; }
17
29
 
18
30
  /* Make code and code blocks a little nicer looking. */
19
31
  code {
20
- padding: 0 .35em;
32
+ padding: .5em !important;
21
33
  background-color: #f3f3f3 !important;
22
34
  border: 1px solid #aaa;
23
35
  border-radius: 3px;
@@ -56,25 +68,22 @@ code {
56
68
  }
57
69
  </style>
58
70
 
59
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
60
- <script src="https://cdn.jsdelivr.net/npm/neatjson@0.10.5/javascript/neatjson.min.js"></script>
61
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.2.0/highlight.min.js" integrity="sha512-TDKKr+IvoqZnPzc3l35hdjpHD0m+b2EC2SrLEgKDRWpxf2rFCxemkgvJ5kfU48ip+Y+m2XVKyOCD85ybtlZDmw==" crossorigin="anonymous"></script>
62
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.2.0/languages/json.min.js" integrity="sha512-FoN8JE+WWCdIGXAIT8KQXwpiavz0Mvjtfk7Rku3MDUNO0BDCiRMXAsSX+e+COFyZTcDb9HDgP+pM2RX12d4j+A==" crossorigin="anonymous"></script>
63
- <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.2.0/languages/xml.min.js" integrity="sha512-dICltIgnUP+QSJrnYGCV8943p3qSDgvcg2NU4W8IcOZP4tdrvxlXjbhIznhtVQEcXow0mOjLM0Q6/NvZsmUH4g==" crossorigin="anonymous"></script>
71
+ <!-- Custom JavaScript -->
64
72
  <script>
65
- hljs.initHighlightingOnLoad()
66
-
67
73
  // What to do when document loads.
68
74
  document.addEventListener("DOMContentLoaded", (event) => {
69
75
  // Pretty-print JSON.
70
76
  [...document.getElementsByClassName("language-json")].forEach((element, index) => {
71
- element.innerHTML = neatJSON(JSON.parse(element.innerHTML), {
77
+ element.innerHTML = neatJSON(JSON.parse(element.innerText), {
72
78
  wrap: 80,
73
79
  afterComma: 1,
74
80
  afterColon: 1,
75
81
  })
76
82
  });
77
83
 
84
+ // Then highlight it.
85
+ hljs.highlightAll();
86
+
78
87
  // Insert copy link and callback to copy contents of `<code>` element.
79
88
  [...document.getElementsByClassName("rrf-copy")].forEach((element, index) => {
80
89
  element.insertAdjacentHTML(
@@ -18,7 +18,7 @@ module RESTFramework::BaseControllerMixin
18
18
  filter_backends: nil,
19
19
  singleton_controller: nil,
20
20
 
21
- # Metadata and display options.
21
+ # Options related to metadata and display.
22
22
  title: nil,
23
23
  description: nil,
24
24
  inflect_acronyms: ["ID", "REST", "API"].freeze,
@@ -36,6 +36,10 @@ module RESTFramework::BaseControllerMixin
36
36
  page_size_query_param: "page_size",
37
37
  max_page_size: nil,
38
38
 
39
+ # Options related to bulk actions and batch processing.
40
+ bulk_guard_query_param: nil,
41
+ enable_batch_processing: nil,
42
+
39
43
  # Option to disable serializer adapters by default, mainly introduced because Active Model
40
44
  # Serializers will do things like serialize `[]` into `{"":[]}`.
41
45
  disable_adapters_by_default: true,
@@ -74,6 +78,13 @@ module RESTFramework::BaseControllerMixin
74
78
  end
75
79
  end
76
80
 
81
+ # Add builtin bulk actions.
82
+ RESTFramework::RRF_BUILTIN_BULK_ACTIONS.each do |action, methods|
83
+ if self.method_defined?(action)
84
+ actions[action] = {path: "", methods: methods, type: :builtin}
85
+ end
86
+ end
87
+
77
88
  # Add extra actions.
78
89
  if extra_actions = self.try(:extra_actions)
79
90
  actions.merge!(RESTFramework::Utils.parse_extra_actions(extra_actions, controller: self))
@@ -268,8 +279,8 @@ module RESTFramework::BaseControllerMixin
268
279
  )
269
280
  end
270
281
 
271
- # Helper to render a browsable API for `html` format, along with basic `json`/`xml` formats, and
272
- # with support or passing custom `kwargs` to the underlying `render` calls.
282
+ # Render a browsable API for `html` format, along with basic `json`/`xml` formats, and with
283
+ # support or passing custom `kwargs` to the underlying `render` calls.
273
284
  def api_response(payload, html_kwargs: nil, **kwargs)
274
285
  html_kwargs ||= {}
275
286
  json_kwargs = kwargs.delete(:json_kwargs) || {}
@@ -0,0 +1,51 @@
1
+ require_relative "models"
2
+
3
+ # Mixin for creating records in bulk. This is unique compared to update/destroy because we overload
4
+ # the existing `create` action to support bulk creation.
5
+ module RESTFramework::BulkCreateModelMixin
6
+ def create
7
+ raise NotImplementedError, "TODO"
8
+ end
9
+
10
+ # Perform the `create!` call and return the created record.
11
+ def create_all!
12
+ raise NotImplementedError, "TODO"
13
+ end
14
+ end
15
+
16
+ # Mixin for updating records in bulk.
17
+ module RESTFramework::BulkUpdateModelMixin
18
+ def update_all
19
+ raise NotImplementedError, "TODO"
20
+ end
21
+
22
+ # Perform the `update!` call and return the updated record.
23
+ def update_all!
24
+ raise NotImplementedError, "TODO"
25
+ end
26
+ end
27
+
28
+ # Mixin for destroying records in bulk.
29
+ module RESTFramework::BulkDestroyModelMixin
30
+ def destroy_all
31
+ raise NotImplementedError, "TODO"
32
+ end
33
+
34
+ # Perform the `destroy!` call and return the destroyed (and frozen) record.
35
+ def destroy_all!
36
+ raise NotImplementedError, "TODO"
37
+ end
38
+ end
39
+
40
+ # Mixin that includes all the CRUD bulk mixins.
41
+ module RESTFramework::BulkModelControllerMixin
42
+ include RESTFramework::ModelControllerMixin
43
+
44
+ include RESTFramework::BulkCreateModelMixin
45
+ include RESTFramework::BulkUpdateModelMixin
46
+ include RESTFramework::BulkDestroyModelMixin
47
+
48
+ def self.included(base)
49
+ RESTFramework::ModelControllerMixin.included(base)
50
+ end
51
+ end
@@ -163,15 +163,20 @@ module RESTFramework::BaseModelControllerMixin
163
163
  # Get metadata about the resource's associations (reflections).
164
164
  def get_associations_metadata
165
165
  return self.get_model.reflections.map { |k, v|
166
+ begin
167
+ pk = v.active_record_primary_key
168
+ rescue ActiveRecord::UnknownPrimaryKey
169
+ end
170
+
166
171
  next [k, {
167
172
  macro: v.macro,
168
173
  label: self.get_label(k),
169
174
  class_name: v.class_name,
170
175
  foreign_key: v.foreign_key,
171
- primary_key: v.active_record_primary_key,
176
+ primary_key: pk,
172
177
  polymorphic: v.polymorphic?,
173
178
  table_name: v.table_name,
174
- options: v.options,
179
+ options: v.options.presence,
175
180
  }.compact]
176
181
  }.to_h
177
182
  end
@@ -238,9 +243,10 @@ module RESTFramework::BaseModelControllerMixin
238
243
  end
239
244
 
240
245
  def self.included(base)
246
+ RESTFramework::BaseControllerMixin.included(base)
247
+
241
248
  return unless base.is_a?(Class)
242
249
 
243
- RESTFramework::BaseControllerMixin.included(base)
244
250
  base.extend(ClassMethods)
245
251
 
246
252
  # Add class attributes (with defaults) unless they already exist.
@@ -311,12 +317,12 @@ module RESTFramework::BaseModelControllerMixin
311
317
  ) || self.get_fields
312
318
  end
313
319
 
314
- # Helper to get the configured serializer class, or `NativeSerializer` as a default.
320
+ # Get the configured serializer class, or `NativeSerializer` as a default.
315
321
  def get_serializer_class
316
322
  return super || RESTFramework::NativeSerializer
317
323
  end
318
324
 
319
- # Helper to get filtering backends, defaulting to using `ModelFilter` and `ModelOrderingFilter`.
325
+ # Get filtering backends, defaulting to using `ModelFilter` and `ModelOrderingFilter`.
320
326
  def get_filter_backends
321
327
  return self.class.filter_backends || [
322
328
  RESTFramework::ModelFilter, RESTFramework::ModelOrderingFilter
@@ -324,14 +330,16 @@ module RESTFramework::BaseModelControllerMixin
324
330
  end
325
331
 
326
332
  # Filter the request body for keys in current action's allowed_parameters/fields config.
327
- def get_body_params
333
+ def get_body_params(request_parameters: nil)
334
+ request_parameters ||= request.request_parameters
335
+
328
336
  # Filter the request body and map to strings. Return all params if we cannot resolve a list of
329
337
  # allowed parameters or fields.
330
338
  allowed_params = self.get_allowed_parameters&.map(&:to_s)
331
339
  body_params = if allowed_params
332
- request.request_parameters.select { |p| allowed_params.include?(p) }
340
+ request_parameters.select { |p| allowed_params.include?(p) }
333
341
  else
334
- request.request_parameters
342
+ request_parameters
335
343
  end
336
344
 
337
345
  # Add query params in place of missing body params, if configured.
@@ -370,7 +378,7 @@ module RESTFramework::BaseModelControllerMixin
370
378
  return @recordset = nil
371
379
  end
372
380
 
373
- # Helper to get the records this controller has access to *after* any filtering is applied.
381
+ # Get the records this controller has access to *after* any filtering is applied.
374
382
  def get_records
375
383
  return @records if instance_variable_defined?(:@records)
376
384
 
@@ -413,7 +421,7 @@ module RESTFramework::ListModelMixin
413
421
  return api_response(self.get_index_records)
414
422
  end
415
423
 
416
- # Helper to get records with both filtering and pagination applied.
424
+ # Get records with both filtering and pagination applied.
417
425
  def get_index_records
418
426
  records = self.get_records
419
427
 
@@ -448,7 +456,7 @@ module RESTFramework::CreateModelMixin
448
456
  return api_response(self.create!, status: :created)
449
457
  end
450
458
 
451
- # Helper to perform the `create!` call and return the created record.
459
+ # Perform the `create!` call and return the created record.
452
460
  def create!
453
461
  if self.get_recordset.respond_to?(:create!) && self.create_from_recordset
454
462
  # Create with any properties inherited from the recordset. We exclude any `select` clauses in
@@ -468,7 +476,7 @@ module RESTFramework::UpdateModelMixin
468
476
  return api_response(self.update!)
469
477
  end
470
478
 
471
- # Helper to perform the `update!` call and return the updated record.
479
+ # Perform the `update!` call and return the updated record.
472
480
  def update!
473
481
  record = self.get_record
474
482
  record.update!(self.get_update_params)
@@ -483,7 +491,7 @@ module RESTFramework::DestroyModelMixin
483
491
  return api_response("")
484
492
  end
485
493
 
486
- # Helper to perform the `destroy!` call and return the destroyed (and frozen) record.
494
+ # Perform the `destroy!` call and return the destroyed (and frozen) record.
487
495
  def destroy!
488
496
  return self.get_record.destroy!
489
497
  end
@@ -493,29 +501,25 @@ end
493
501
  module RESTFramework::ReadOnlyModelControllerMixin
494
502
  include RESTFramework::BaseModelControllerMixin
495
503
 
496
- def self.included(base)
497
- return unless base.is_a?(Class)
504
+ include RESTFramework::ListModelMixin
505
+ include RESTFramework::ShowModelMixin
498
506
 
507
+ def self.included(base)
499
508
  RESTFramework::BaseModelControllerMixin.included(base)
500
509
  end
501
-
502
- include RESTFramework::ListModelMixin
503
- include RESTFramework::ShowModelMixin
504
510
  end
505
511
 
506
512
  # Mixin that includes all the CRUD mixins.
507
513
  module RESTFramework::ModelControllerMixin
508
514
  include RESTFramework::BaseModelControllerMixin
509
515
 
510
- def self.included(base)
511
- return unless base.is_a?(Class)
512
-
513
- RESTFramework::BaseModelControllerMixin.included(base)
514
- end
515
-
516
516
  include RESTFramework::ListModelMixin
517
517
  include RESTFramework::ShowModelMixin
518
518
  include RESTFramework::CreateModelMixin
519
519
  include RESTFramework::UpdateModelMixin
520
520
  include RESTFramework::DestroyModelMixin
521
+
522
+ def self.included(base)
523
+ RESTFramework::BaseModelControllerMixin.included(base)
524
+ end
521
525
  end
@@ -2,4 +2,5 @@ module RESTFramework::ControllerMixins
2
2
  end
3
3
 
4
4
  require_relative "controller_mixins/base"
5
+ require_relative "controller_mixins/bulk"
5
6
  require_relative "controller_mixins/models"
@@ -102,6 +102,15 @@ module ActionDispatch::Routing
102
102
  public_send(m, "", action: action) if self.respond_to?(m)
103
103
  end
104
104
  end
105
+
106
+ # Route bulk actions, if configured.
107
+ RESTFramework::RRF_BUILTIN_BULK_ACTIONS.each do |action, methods|
108
+ next unless controller_class.method_defined?(action)
109
+
110
+ [methods].flatten.each do |m|
111
+ public_send(m, "", action: action) if self.respond_to?(m)
112
+ end
113
+ end
105
114
  end
106
115
 
107
116
  if unscoped
@@ -88,7 +88,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
88
88
  return self.config || self.singular_config || self.plural_config
89
89
  end
90
90
 
91
- # Helper to get a native serializer configuration from the controller.
91
+ # Get a native serializer configuration from the controller.
92
92
  def get_controller_native_serializer_config
93
93
  return nil unless @controller
94
94
 
@@ -101,9 +101,8 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
101
101
  return controller_serializer || @controller.class.try(:native_serializer_config)
102
102
  end
103
103
 
104
- # Helper to filter a single subconfig for specific keys. By default, keys from `fields` are
105
- # removed from the provided `subcfg`. There are two (mutually exclusive) options to adjust the
106
- # behavior:
104
+ # Filter a single subconfig for specific keys. By default, keys from `fields` are removed from the
105
+ # provided `subcfg`. There are two (mutually exclusive) options to adjust the behavior:
107
106
  #
108
107
  # `add`: Add any `fields` to the `subcfg` which aren't already in the `subcfg`.
109
108
  # `only`: Remove any values found in the `subcfg` not in `fields`.
@@ -149,7 +148,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
149
148
  return subcfg
150
149
  end
151
150
 
152
- # Helper to filter out configuration properties based on the :except query parameter.
151
+ # Filter out configuration properties based on the :except query parameter.
153
152
  def filter_except(cfg)
154
153
  return cfg unless @controller
155
154
 
@@ -245,7 +244,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
245
244
  return filter_except(self._get_raw_serializer_config)
246
245
  end
247
246
 
248
- # Internal helper to serialize a single record and merge results of `serializer_methods`.
247
+ # Serialize a single record and merge results of `serializer_methods`.
249
248
  def _serialize(record, config, serializer_methods)
250
249
  # Ensure serializer_methods is either falsy, or an array.
251
250
  if serializer_methods && !serializer_methods.respond_to?(:to_ary)
@@ -13,6 +13,10 @@ module RESTFramework
13
13
  RRF_BUILTIN_ACTIONS = {
14
14
  options: :options,
15
15
  }.freeze
16
+ RRF_BUILTIN_BULK_ACTIONS = {
17
+ update_all: [:put, :patch].freeze,
18
+ destroy_all: :delete,
19
+ }.freeze
16
20
 
17
21
  # Global configuration should be kept minimal, as controller-level configurations allows multiple
18
22
  # APIs to be defined to behave differently.
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.7.4
4
+ version: 0.7.5
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: 2023-01-10 00:00:00.000000000 Z
11
+ date: 2023-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -42,6 +42,7 @@ files:
42
42
  - lib/rest_framework.rb
43
43
  - lib/rest_framework/controller_mixins.rb
44
44
  - lib/rest_framework/controller_mixins/base.rb
45
+ - lib/rest_framework/controller_mixins/bulk.rb
45
46
  - lib/rest_framework/controller_mixins/models.rb
46
47
  - lib/rest_framework/engine.rb
47
48
  - lib/rest_framework/errors.rb