rest_framework 0.9.2 → 0.9.3

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/app/views/rest_framework/_external.html.erb +13 -0
  4. data/app/views/rest_framework/_head.html.erb +2 -192
  5. data/{docs/assets/js/rest_framework.js → app/views/rest_framework/_shared.html} +69 -42
  6. data/docs/Gemfile +1 -0
  7. data/docs/Gemfile.lock +14 -14
  8. data/docs/_config.yml +4 -2
  9. data/docs/_guide/2_controllers.md +342 -0
  10. data/docs/_guide/3_serializers.md +1 -1
  11. data/docs/_guide/4_filtering_and_ordering.md +8 -8
  12. data/docs/_includes/external.html +9 -0
  13. data/docs/_includes/head.html +135 -15
  14. data/docs/_includes/shared.html +164 -0
  15. data/lib/rest_framework/controller_mixins/base.rb +23 -36
  16. data/lib/rest_framework/controller_mixins/models.rb +86 -75
  17. data/lib/rest_framework/controller_mixins.rb +1 -0
  18. data/lib/rest_framework/engine.rb +9 -0
  19. data/lib/rest_framework/filters/base.rb +9 -0
  20. data/lib/rest_framework/filters/model_ordering.rb +48 -0
  21. data/lib/rest_framework/filters/model_query.rb +51 -0
  22. data/lib/rest_framework/filters/model_search.rb +41 -0
  23. data/lib/rest_framework/filters/ransack.rb +25 -0
  24. data/lib/rest_framework/filters.rb +6 -150
  25. data/lib/rest_framework/paginators.rb +7 -11
  26. data/lib/rest_framework/serializers.rb +10 -10
  27. data/lib/rest_framework/utils.rb +15 -7
  28. data/lib/rest_framework.rb +93 -4
  29. data/vendor/assets/javascripts/rest_framework/bootstrap.js +7 -0
  30. data/vendor/assets/javascripts/rest_framework/highlight-json.js +7 -0
  31. data/vendor/assets/javascripts/rest_framework/highlight-xml.js +29 -0
  32. data/vendor/assets/javascripts/rest_framework/highlight.js +1202 -0
  33. data/vendor/assets/javascripts/rest_framework/neatjson.js +8 -0
  34. data/vendor/assets/javascripts/rest_framework/trix.js +6 -0
  35. data/vendor/assets/stylesheets/rest_framework/bootstrap-icons.css +13 -0
  36. data/vendor/assets/stylesheets/rest_framework/bootstrap.css +6 -0
  37. data/vendor/assets/stylesheets/rest_framework/highlight-a11y-dark.css +7 -0
  38. data/vendor/assets/stylesheets/rest_framework/highlight-a11y-light.css +7 -0
  39. data/vendor/assets/stylesheets/rest_framework/trix.css +410 -0
  40. metadata +23 -5
  41. data/docs/_guide/2_controller_mixins.md +0 -293
  42. data/docs/assets/css/rest_framework.css +0 -159
@@ -0,0 +1,48 @@
1
+ # A filter backend which handles ordering of the recordset.
2
+ class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
3
+ # Get a list of ordering fields for the current action.
4
+ def _get_fields
5
+ return @controller.ordering_fields&.map(&:to_s) || @controller.get_fields
6
+ end
7
+
8
+ # Convert ordering string to an ordering configuration.
9
+ def _get_ordering
10
+ return nil if @controller.ordering_query_param.blank?
11
+
12
+ # Ensure ordering_fields are strings since the split param will be strings.
13
+ fields = self._get_fields
14
+ order_string = @controller.params[@controller.ordering_query_param]
15
+
16
+ if order_string.present?
17
+ ordering = {}.with_indifferent_access
18
+ order_string.split(",").each do |field|
19
+ if field[0] == "-"
20
+ column = field[1..-1]
21
+ direction = :desc
22
+ else
23
+ column = field
24
+ direction = :asc
25
+ end
26
+
27
+ next if !column.in?(fields) && column.split(".").first.in?(fields)
28
+
29
+ ordering[column] = direction
30
+ end
31
+ return ordering
32
+ end
33
+
34
+ return nil
35
+ end
36
+
37
+ # Order data according to the request query parameters.
38
+ def get_filtered_data(data)
39
+ ordering = self._get_ordering
40
+ reorder = !@controller.ordering_no_reorder
41
+
42
+ if ordering && !ordering.empty?
43
+ return data.send(reorder ? :reorder : :order, ordering)
44
+ end
45
+
46
+ return data
47
+ end
48
+ end
@@ -0,0 +1,51 @@
1
+ # A simple filtering backend that supports filtering a recordset based on query parameters.
2
+ class RESTFramework::ModelQueryFilter < RESTFramework::BaseFilter
3
+ # Get a list of filterset fields for the current action.
4
+ def _get_fields
5
+ # Always return a list of strings; `@controller.get_fields` already does this.
6
+ return @controller.class.filterset_fields&.map(&:to_s) || @controller.get_fields
7
+ end
8
+
9
+ # Filter params for keys allowed by the current action's filterset_fields/fields config.
10
+ def _get_filter_params
11
+ fields = self._get_fields
12
+
13
+ return @controller.request.query_parameters.select { |p, _|
14
+ # Remove any trailing `__in` from the field name.
15
+ field = p.chomp("__in")
16
+
17
+ # Remove any associations whose sub-fields are not filterable.
18
+ if match = /(.*)\.(.*)/.match(field)
19
+ field, sub_field = match[1..2]
20
+ next false unless field.in?(fields)
21
+
22
+ sub_fields = @controller.class.get_field_config(field)[:sub_fields] || []
23
+ next sub_field.in?(sub_fields)
24
+ end
25
+
26
+ next field.in?(fields)
27
+ }.map { |p, v|
28
+ # Convert fields ending in `__in` to array values.
29
+ if p.end_with?("__in")
30
+ p = p.chomp("__in")
31
+ v = v.split(",")
32
+ end
33
+
34
+ # Convert "nil" and "null" to nil.
35
+ if v == "nil" || v == "null"
36
+ v = nil
37
+ end
38
+
39
+ [p, v]
40
+ }.to_h.symbolize_keys
41
+ end
42
+
43
+ # Filter data according to the request query parameters.
44
+ def get_filtered_data(data)
45
+ if filter_params = self._get_filter_params.presence
46
+ return data.where(**filter_params)
47
+ end
48
+
49
+ return data
50
+ end
51
+ end
@@ -0,0 +1,41 @@
1
+ # Multi-field text searching on models.
2
+ class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
3
+ # Get a list of search fields for the current action.
4
+ def _get_fields
5
+ if search_fields = @controller.search_fields
6
+ return search_fields&.map(&:to_s)
7
+ end
8
+
9
+ columns = @controller.class.get_model.column_names
10
+ return @controller.get_fields.select { |f|
11
+ f.in?(RESTFramework.config.search_columns) && f.in?(columns)
12
+ }
13
+ end
14
+
15
+ # Filter data according to the request query parameters.
16
+ def get_filtered_data(data)
17
+ search = @controller.request.query_parameters[@controller.search_query_param]
18
+
19
+ if search.present?
20
+ if fields = self._get_fields.presence
21
+ # MySQL doesn't support casting to VARCHAR, so we need to use CHAR instead.
22
+ data_type = if data.connection.adapter_name =~ /mysql/i
23
+ "CHAR"
24
+ else
25
+ # Sufficient for both PostgreSQL and SQLite.
26
+ "VARCHAR"
27
+ end
28
+
29
+ # Ensure we pass user input as arguments to prevent SQL injection.
30
+ return data.where(
31
+ fields.map { |f|
32
+ "CAST(#{f} AS #{data_type}) #{@controller.search_ilike ? "ILIKE" : "LIKE"} ?"
33
+ }.join(" OR "),
34
+ *(["%#{search}%"] * fields.length),
35
+ )
36
+ end
37
+ end
38
+
39
+ return data
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ # Adapter for the `ransack` gem.
2
+ class RESTFramework::RansackFilter < RESTFramework::BaseFilter
3
+ # Filter data according to the request query parameters.
4
+ def get_filtered_data(data)
5
+ q = @controller.request.query_parameters[@controller.ransack_query_param]
6
+
7
+ if q.present?
8
+ distinct = @controller.ransack_distinct
9
+
10
+ # Determine if `distinct` is determined by query param.
11
+ if distinct_query_param = @controller.ransack_distinct_query_param
12
+ distinct_from_query = ActiveRecord::Type::Boolean.new.cast(
13
+ @controller.request.query_parameters[distinct_query_param],
14
+ )
15
+ unless distinct_from_query.nil?
16
+ distinct = distinct_from_query
17
+ end
18
+ end
19
+
20
+ return data.ransack(q, @controller.ransack_options).result(distinct: distinct)
21
+ end
22
+
23
+ return data
24
+ end
25
+ end
@@ -1,153 +1,9 @@
1
- class RESTFramework::BaseFilter
2
- def initialize(controller:)
3
- @controller = controller
4
- end
5
-
6
- def get_filtered_data(data)
7
- raise NotImplementedError
8
- end
9
- end
10
-
11
- # A simple filtering backend that supports filtering a recordset based on fields defined on the
12
- # controller class.
13
- class RESTFramework::ModelFilter < RESTFramework::BaseFilter
14
- # Get a list of filterset fields for the current action.
15
- def _get_fields
16
- # Always return a list of strings; `@controller.get_fields` already does this.
17
- return @controller.class.filterset_fields&.map(&:to_s) || @controller.get_fields
18
- end
19
-
20
- # Filter params for keys allowed by the current action's filterset_fields/fields config.
21
- def _get_filter_params
22
- fields = self._get_fields
23
-
24
- return @controller.request.query_parameters.select { |p, _|
25
- # Remove any trailing `__in` from the field name.
26
- field = p.chomp("__in")
27
-
28
- # Remove any associations whose sub-fields are not filterable.
29
- if match = /(.*)\.(.*)/.match(field)
30
- field, sub_field = match[1..2]
31
- next false unless field.in?(fields)
32
-
33
- sub_fields = @controller.class.get_field_config(field)[:sub_fields] || []
34
- next sub_field.in?(sub_fields)
35
- end
36
-
37
- next field.in?(fields)
38
- }.map { |p, v|
39
- # Convert fields ending in `__in` to array values.
40
- if p.end_with?("__in")
41
- p = p.chomp("__in")
42
- v = v.split(",")
43
- end
44
-
45
- # Convert "nil" and "null" to nil.
46
- if v == "nil" || v == "null"
47
- v = nil
48
- end
49
-
50
- [p, v]
51
- }.to_h.symbolize_keys
52
- end
53
-
54
- # Filter data according to the request query parameters.
55
- def get_filtered_data(data)
56
- if filter_params = self._get_filter_params.presence
57
- return data.where(**filter_params)
58
- end
59
-
60
- return data
61
- end
62
- end
63
-
64
- # A filter backend which handles ordering of the recordset.
65
- class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
66
- # Get a list of ordering fields for the current action.
67
- def _get_fields
68
- return @controller.class.ordering_fields&.map(&:to_s) || @controller.get_fields
69
- end
70
-
71
- # Convert ordering string to an ordering configuration.
72
- def _get_ordering
73
- return nil if @controller.class.ordering_query_param.blank?
74
-
75
- # Ensure ordering_fields are strings since the split param will be strings.
76
- fields = self._get_fields
77
- order_string = @controller.params[@controller.class.ordering_query_param]
78
-
79
- if order_string.present?
80
- ordering = {}.with_indifferent_access
81
- order_string.split(",").each do |field|
82
- if field[0] == "-"
83
- column = field[1..-1]
84
- direction = :desc
85
- else
86
- column = field
87
- direction = :asc
88
- end
89
-
90
- next if !column.in?(fields) && column.split(".").first.in?(fields)
91
-
92
- ordering[column] = direction
93
- end
94
- return ordering
95
- end
96
-
97
- return nil
98
- end
99
-
100
- # Order data according to the request query parameters.
101
- def get_filtered_data(data)
102
- ordering = self._get_ordering
103
- reorder = !@controller.class.ordering_no_reorder
104
-
105
- if ordering && !ordering.empty?
106
- return data.send(reorder ? :reorder : :order, ordering)
107
- end
108
-
109
- return data
110
- end
1
+ module RESTFramework::Filters
111
2
  end
112
3
 
113
- # Multi-field text searching on models.
114
- class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
115
- # Get a list of search fields for the current action.
116
- def _get_fields
117
- if search_fields = @controller.class.search_fields
118
- return search_fields&.map(&:to_s)
119
- end
120
-
121
- columns = @controller.class.get_model.column_names
122
- return @controller.get_fields.select { |f|
123
- f.in?(RESTFramework.config.search_columns) && f.in?(columns)
124
- }
125
- end
126
-
127
- # Filter data according to the request query parameters.
128
- def get_filtered_data(data)
129
- search = @controller.request.query_parameters[@controller.class.search_query_param]
4
+ require_relative "filters/base"
130
5
 
131
- if search.present?
132
- if fields = self._get_fields.presence
133
- # MySQL doesn't support casting to VARCHAR, so we need to use CHAR instead.
134
- data_type = if data.connection.adapter_name =~ /mysql/i
135
- "CHAR"
136
- else
137
- # Sufficient for both PostgreSQL and SQLite.
138
- "VARCHAR"
139
- end
140
-
141
- # Ensure we pass user input as arguments to prevent SQL injection.
142
- return data.where(
143
- fields.map { |f|
144
- "CAST(#{f} AS #{data_type}) #{@controller.class.search_ilike ? "ILIKE" : "LIKE"} ?"
145
- }.join(" OR "),
146
- *(["%#{search}%"] * fields.length),
147
- )
148
- end
149
- end
150
-
151
- return data
152
- end
153
- end
6
+ require_relative "filters/model_ordering"
7
+ require_relative "filters/model_query"
8
+ require_relative "filters/model_search"
9
+ require_relative "filters/ransack"
@@ -33,35 +33,31 @@ class RESTFramework::PageNumberPaginator < RESTFramework::BasePaginator
33
33
  page_size = nil
34
34
 
35
35
  # Get from context, if allowed.
36
- if @controller.class.page_size_query_param
37
- if page_size = @controller.params[@controller.class.page_size_query_param].presence
36
+ if @controller.page_size_query_param
37
+ if page_size = @controller.params[@controller.page_size_query_param].presence
38
38
  page_size = page_size.to_i
39
39
  end
40
40
  end
41
41
 
42
42
  # Otherwise, get from config.
43
- if !page_size && @controller.class.page_size
44
- page_size = @controller.class.page_size
43
+ if !page_size && @controller.page_size
44
+ page_size = @controller.page_size
45
45
  end
46
46
 
47
47
  # Ensure we don't exceed the max page size.
48
- if @controller.class.max_page_size && page_size > @controller.class.max_page_size
49
- page_size = @controller.class.max_page_size
48
+ if @controller.max_page_size && page_size > @controller.max_page_size
49
+ page_size = @controller.max_page_size
50
50
  end
51
51
 
52
52
  # Ensure we return at least 1.
53
53
  return page_size.zero? ? 1 : page_size
54
54
  end
55
55
 
56
- def _page_query_param
57
- return @controller.class.page_query_param&.to_sym
58
- end
59
-
60
56
  # Get the page and return it so the caller can serialize it.
61
57
  def get_page(page_number=nil)
62
58
  # If page number isn't provided, infer from the params or use 1 as a fallback value.
63
59
  unless page_number
64
- page_number = @controller&.params&.[](self._page_query_param)
60
+ page_number = @controller&.params&.[](@controller.page_query_param&.to_sym)
65
61
  if page_number.blank?
66
62
  page_number = 1
67
63
  else
@@ -93,12 +93,12 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
93
93
  return nil unless @controller
94
94
 
95
95
  if @many == true
96
- controller_serializer = @controller.class.try(:native_serializer_plural_config)
96
+ controller_serializer = @controller.try(:native_serializer_plural_config)
97
97
  elsif @many == false
98
- controller_serializer = @controller.class.try(:native_serializer_singular_config)
98
+ controller_serializer = @controller.try(:native_serializer_singular_config)
99
99
  end
100
100
 
101
- return controller_serializer || @controller.class.try(:native_serializer_config)
101
+ return controller_serializer || @controller.try(:native_serializer_config)
102
102
  end
103
103
 
104
104
  # Filter a single subconfig for specific keys. By default, keys from `fields` are removed from the
@@ -152,9 +152,9 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
152
152
  def filter_from_request(cfg)
153
153
  return cfg unless @controller
154
154
 
155
- except_param = @controller.class.try(:native_serializer_except_query_param)
156
- only_param = @controller.class.try(:native_serializer_only_query_param)
157
- if except_param && except = @controller.request.query_parameters[except_param].presence
155
+ except_param = @controller.try(:native_serializer_except_query_param)
156
+ only_param = @controller.try(:native_serializer_only_query_param)
157
+ if except_param && except = @controller.request&.query_parameters&.[](except_param).presence
158
158
  if except = except.split(",").map(&:strip).map(&:to_sym).presence
159
159
  # Filter `only`, `except` (additive), `include`, `methods`, and `serializer_methods`.
160
160
  if cfg[:only]
@@ -171,7 +171,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
171
171
  cfg[:serializer_methods], fields: except
172
172
  )
173
173
  end
174
- elsif only_param && only = @controller.request.query_parameters[only_param].presence
174
+ elsif only_param && only = @controller.request&.query_parameters&.[](only_param).presence
175
175
  if only = only.split(",").map(&:strip).map(&:to_sym).presence
176
176
  # Filter `only`, `include`, and `methods`. Adding anything to `except` is not needed,
177
177
  # because any configuration there takes precedence over `only`.
@@ -196,10 +196,10 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
196
196
  def _get_associations_limit
197
197
  return @_get_associations_limit if defined?(@_get_associations_limit)
198
198
 
199
- limit = @controller.class.native_serializer_associations_limit
199
+ limit = @controller&.native_serializer_associations_limit
200
200
 
201
201
  # Extract the limit from the query parameters if it's set.
202
- if query_param = @controller.class.native_serializer_associations_limit_query_param
202
+ if query_param = @controller&.native_serializer_associations_limit_query_param
203
203
  if @controller.request.query_parameters.key?(query_param)
204
204
  query_limit = @controller.request.query_parameters[query_param].to_i
205
205
  if query_limit > 0
@@ -256,7 +256,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
256
256
  end
257
257
 
258
258
  # If we need to include the association count, then add it here.
259
- if @controller.class.native_serializer_include_associations_count
259
+ if @controller.native_serializer_include_associations_count
260
260
  method_name = "#{f}.count"
261
261
  serializer_methods[method_name] = method_name
262
262
  self.define_singleton_method(method_name) do |record|
@@ -154,8 +154,8 @@ module RESTFramework::Utils
154
154
  parsed_fields = fields_hash[:only] || (
155
155
  model ? self.fields_for(model, exclude_associations: exclude_associations) : []
156
156
  )
157
- parsed_fields += fields_hash[:include] if fields_hash[:include]
158
- parsed_fields -= fields_hash[:exclude] if fields_hash[:exclude]
157
+ parsed_fields += fields_hash[:include].map(&:to_s) if fields_hash[:include]
158
+ parsed_fields -= fields_hash[:exclude].map(&:to_s) if fields_hash[:exclude]
159
159
 
160
160
  # Warn for any unknown keys.
161
161
  (fields_hash.keys - [:only, :include, :exclude]).each do |k|
@@ -185,11 +185,6 @@ module RESTFramework::Utils
185
185
  next nil
186
186
  end
187
187
 
188
- # Exclude user-specified associations.
189
- if ref.class_name.in?(RESTFramework.config.exclude_association_classes)
190
- next nil
191
- end
192
-
193
188
  if ref.collection? && RESTFramework.config.large_reverse_association_tables&.include?(
194
189
  ref.table_name,
195
190
  )
@@ -225,4 +220,17 @@ module RESTFramework::Utils
225
220
 
226
221
  return ["id", "name"]
227
222
  end
223
+
224
+ # Get a field's id/ids variation.
225
+ def self.get_id_field(field, reflection)
226
+ if reflection.collection?
227
+ return "#{field.singularize}_ids"
228
+ elsif reflection.belongs_to?
229
+ # The id field for belongs_to is always the foreign key column name, even if the
230
+ # association is named differently.
231
+ return reflection.foreign_key
232
+ end
233
+
234
+ return nil
235
+ end
228
236
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RESTFramework
2
4
  BUILTIN_ACTIONS = {
3
5
  index: :get,
@@ -18,6 +20,93 @@ module RESTFramework
18
20
  destroy_all: :delete,
19
21
  }.freeze
20
22
 
23
+ # rubocop:disable Layout/LineLength
24
+ EXTERNAL_ASSETS = {
25
+ # Bootstrap
26
+ "bootstrap.css" => {
27
+ url: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/css/bootstrap.min.css",
28
+ sri: "sha384-aFq/bzH65dt+w6FI2ooMVUpc+21e0SRygnTpmBvdBgSdnuTN7QbdgL+OapgHtvPp",
29
+ },
30
+ "bootstrap.js" => {
31
+ url: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/js/bootstrap.bundle.min.js",
32
+ sri: "sha384-qKXV1j0HvMUeCBQ+QVp7JcfGl760yU08IQ+GpUo5hlbpg51QRiuqHAJz8+BrxE/N",
33
+ },
34
+
35
+ # Bootstrap Icons
36
+ "bootstrap-icons.css" => {
37
+ url: "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.min.css",
38
+ inline_fonts: true,
39
+ },
40
+
41
+ # Highlight.js
42
+ "highlight.js" => {
43
+ url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js",
44
+ sri: "sha512-bgHRAiTjGrzHzLyKOnpFvaEpGzJet3z4tZnXGjpsCcqOnAH6VGUx9frc5bcIhKTVLEiCO6vEhNAgx5jtLUYrfA==",
45
+ },
46
+ "highlight-json.js" => {
47
+ url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/json.min.js",
48
+ sri: "sha512-0xYvyncS9OLE7GOpNBZFnwyh9+bq4HVgk4yVVYI678xRvE22ASicF1v6fZ1UiST+M6pn17MzFZdvVCI3jTHSyw==",
49
+ },
50
+ "highlight-xml.js" => {
51
+ url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/xml.min.js",
52
+ sri: "sha512-5zBcw+OKRkaNyvUEPlTSfYylVzgpi7KpncY36b0gRudfxIYIH0q0kl2j26uCUB3YBRM6ytQQEZSgRg+ZlBTmdA==",
53
+ },
54
+ "highlight-a11y-dark.css" => {
55
+ url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/a11y-dark.min.css",
56
+ sri: "sha512-Vj6gPCk8EZlqnoveEyuGyYaWZ1+jyjMPg8g4shwyyNlRQl6d3L9At02ZHQr5K6s5duZl/+YKMnM3/8pDhoUphg==",
57
+ extra_tag_attrs: {class: "rrf-dark-mode"},
58
+ },
59
+ "highlight-a11y-light.css" => {
60
+ url: "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/a11y-light.min.css",
61
+ sri: "sha512-WDk6RzwygsN9KecRHAfm9HTN87LQjqdygDmkHSJxVkVI7ErCZ8ZWxP6T8RvBujY1n2/E4Ac+bn2ChXnp5rnnHA==",
62
+ extra_tag_attrs: {class: "rrf-light-mode"},
63
+ },
64
+
65
+ # NeatJSON
66
+ "neatjson.js" => {
67
+ url: "https://cdn.jsdelivr.net/npm/neatjson@0.10.5/javascript/neatjson.min.js",
68
+ exclude_from_docs: true,
69
+ },
70
+
71
+ # Trix
72
+ "trix.css" => {
73
+ url: "https://unpkg.com/trix@2.0.0/dist/trix.css",
74
+ exclude_from_docs: true,
75
+ },
76
+ "trix.js" => {
77
+ url: "https://unpkg.com/trix@2.0.0/dist/trix.umd.min.js",
78
+ exclude_from_docs: true,
79
+ },
80
+ }.map { |name, cfg|
81
+ if File.extname(name) == ".js"
82
+ cfg[:place] = "javascripts"
83
+ cfg[:extra_tag_attrs] ||= {}
84
+ cfg[:tag_attrs] = {
85
+ src: cfg[:url],
86
+ integrity: cfg[:sri],
87
+ crossorigin: "anonymous",
88
+ referrerpolicy: "no-referrer",
89
+ defer: true,
90
+ **cfg[:extra_tag_attrs],
91
+ }
92
+ cfg[:tag] = ActionController::Base.helpers.tag.script(**cfg[:tag_attrs])
93
+ else
94
+ cfg[:place] = "stylesheets"
95
+ cfg[:extra_tag_attrs] ||= {}
96
+ cfg[:tag_attrs] = {
97
+ rel: "stylesheet",
98
+ href: cfg[:url],
99
+ integrity: cfg[:sri],
100
+ crossorigin: "anonymous",
101
+ **cfg[:extra_tag_attrs],
102
+ }
103
+ cfg[:tag] = ActionController::Base.helpers.tag.link(**cfg[:tag_attrs])
104
+ end
105
+
106
+ [name, cfg]
107
+ }.to_h.freeze
108
+ # rubocop:enable Layout/LineLength
109
+
21
110
  # Global configuration should be kept minimal, as controller-level configurations allows multiple
22
111
  # APIs to be defined to behave differently.
23
112
  class Config
@@ -48,18 +137,18 @@ module RESTFramework
48
137
  # Disable `rescue_from` on the controller mixins.
49
138
  attr_accessor :disable_rescue_from
50
139
 
51
- # Exclude certain classes from being added by default as association fields.
52
- attr_accessor :exclude_association_classes
53
-
54
140
  # The default label fields to use when generating labels for `has_many` associations.
55
141
  attr_accessor :label_fields
56
142
 
57
143
  # The default search columns to use when generating search filters.
58
144
  attr_accessor :search_columns
59
145
 
146
+ # Option to use vendored assets (requires sprockets or propshaft) rather than linking to
147
+ # external assets (the default).
148
+ attr_accessor :use_vendored_assets
149
+
60
150
  def initialize
61
151
  self.show_backtrace = Rails.env.development?
62
- self.exclude_association_classes = DEFAULT_EXCLUDE_ASSOCIATION_CLASSES
63
152
  self.label_fields = DEFAULT_LABEL_FIELDS
64
153
  self.search_columns = DEFAULT_SEARCH_COLUMNS
65
154
  end