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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/app/views/rest_framework/_external.html.erb +13 -0
- data/app/views/rest_framework/_head.html.erb +2 -192
- data/{docs/assets/js/rest_framework.js → app/views/rest_framework/_shared.html} +69 -42
- data/docs/Gemfile +1 -0
- data/docs/Gemfile.lock +14 -14
- data/docs/_config.yml +4 -2
- data/docs/_guide/2_controllers.md +342 -0
- data/docs/_guide/3_serializers.md +1 -1
- data/docs/_guide/4_filtering_and_ordering.md +8 -8
- data/docs/_includes/external.html +9 -0
- data/docs/_includes/head.html +135 -15
- data/docs/_includes/shared.html +164 -0
- data/lib/rest_framework/controller_mixins/base.rb +23 -36
- data/lib/rest_framework/controller_mixins/models.rb +86 -75
- data/lib/rest_framework/controller_mixins.rb +1 -0
- data/lib/rest_framework/engine.rb +9 -0
- data/lib/rest_framework/filters/base.rb +9 -0
- data/lib/rest_framework/filters/model_ordering.rb +48 -0
- data/lib/rest_framework/filters/model_query.rb +51 -0
- data/lib/rest_framework/filters/model_search.rb +41 -0
- data/lib/rest_framework/filters/ransack.rb +25 -0
- data/lib/rest_framework/filters.rb +6 -150
- data/lib/rest_framework/paginators.rb +7 -11
- data/lib/rest_framework/serializers.rb +10 -10
- data/lib/rest_framework/utils.rb +15 -7
- data/lib/rest_framework.rb +93 -4
- data/vendor/assets/javascripts/rest_framework/bootstrap.js +7 -0
- data/vendor/assets/javascripts/rest_framework/highlight-json.js +7 -0
- data/vendor/assets/javascripts/rest_framework/highlight-xml.js +29 -0
- data/vendor/assets/javascripts/rest_framework/highlight.js +1202 -0
- data/vendor/assets/javascripts/rest_framework/neatjson.js +8 -0
- data/vendor/assets/javascripts/rest_framework/trix.js +6 -0
- data/vendor/assets/stylesheets/rest_framework/bootstrap-icons.css +13 -0
- data/vendor/assets/stylesheets/rest_framework/bootstrap.css +6 -0
- data/vendor/assets/stylesheets/rest_framework/highlight-a11y-dark.css +7 -0
- data/vendor/assets/stylesheets/rest_framework/highlight-a11y-light.css +7 -0
- data/vendor/assets/stylesheets/rest_framework/trix.css +410 -0
- metadata +23 -5
- data/docs/_guide/2_controller_mixins.md +0 -293
- 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
|
-
|
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
|
-
|
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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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.
|
37
|
-
if page_size = @controller.params[@controller.
|
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.
|
44
|
-
page_size = @controller.
|
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.
|
49
|
-
page_size = @controller.
|
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&.[](
|
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.
|
96
|
+
controller_serializer = @controller.try(:native_serializer_plural_config)
|
97
97
|
elsif @many == false
|
98
|
-
controller_serializer = @controller.
|
98
|
+
controller_serializer = @controller.try(:native_serializer_singular_config)
|
99
99
|
end
|
100
100
|
|
101
|
-
return controller_serializer || @controller.
|
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.
|
156
|
-
only_param = @controller.
|
157
|
-
if except_param && except = @controller.request
|
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
|
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
|
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
|
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.
|
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|
|
data/lib/rest_framework/utils.rb
CHANGED
@@ -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
|
data/lib/rest_framework.rb
CHANGED
@@ -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
|