rest_framework 0.7.4 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
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