rest_framework 0.9.2 → 0.9.3

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