rest_framework 0.9.16 → 0.11.0

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.
@@ -13,34 +13,35 @@ class RESTFramework::Paginators::PageNumberPaginator < RESTFramework::Paginators
13
13
  end
14
14
 
15
15
  def _page_size
16
- page_size = nil
16
+ page_size = 1
17
17
 
18
18
  # Get from context, if allowed.
19
- if @controller.page_size_query_param
20
- if page_size = @controller.params[@controller.page_size_query_param].presence
19
+ if param = @controller.class.page_size_query_param
20
+ if page_size = @controller.params[param].presence
21
21
  page_size = page_size.to_i
22
22
  end
23
23
  end
24
24
 
25
25
  # Otherwise, get from config.
26
- if !page_size && @controller.page_size
27
- page_size = @controller.page_size
26
+ if !page_size && @controller.class.page_size
27
+ page_size = @controller.class.page_size.to_i
28
28
  end
29
29
 
30
30
  # Ensure we don't exceed the max page size.
31
- if @controller.max_page_size && page_size > @controller.max_page_size
32
- page_size = @controller.max_page_size
31
+ max_page_size = @controller.class.max_page_size&.to_i
32
+ if max_page_size && page_size > max_page_size
33
+ page_size = max_page_size
33
34
  end
34
35
 
35
36
  # Ensure we return at least 1.
36
- return page_size.zero? ? 1 : page_size
37
+ return [page_size, 1].max
37
38
  end
38
39
 
39
40
  # Get the page and return it so the caller can serialize it.
40
41
  def get_page(page_number=nil)
41
42
  # If page number isn't provided, infer from the params or use 1 as a fallback value.
42
43
  unless page_number
43
- page_number = @controller&.params&.[](@controller.page_query_param&.to_sym)
44
+ page_number = @controller&.params&.[](@controller.class.page_query_param&.to_sym)
44
45
  if page_number.blank?
45
46
  page_number = 1
46
47
  else
@@ -57,9 +58,9 @@ class RESTFramework::Paginators::PageNumberPaginator < RESTFramework::Paginators
57
58
  return @data.limit(@page_size).offset(page_index * @page_size)
58
59
  end
59
60
 
60
- # Wrap the serialized page with appropriate metadata. TODO: include links.
61
+ # Wrap the serialized page with appropriate metadata.
61
62
  def get_paginated_response(serialized_page)
62
- page_query_param = @controller.page_query_param
63
+ page_query_param = @controller.class.page_query_param
63
64
  base_params = @controller.params.to_unsafe_h
64
65
  next_url = if @page_number < @total_pages
65
66
  @controller.url_for({**base_params, page_query_param => @page_number + 1})
@@ -27,7 +27,17 @@ module ActionDispatch::Routing
27
27
  controller = mod.const_get(name)
28
28
  rescue NameError
29
29
  if fallback_reverse_pluralization
30
- controller = mod.const_get(name_reverse)
30
+ reraise = false
31
+
32
+ begin
33
+ controller = mod.const_get(name_reverse)
34
+ rescue
35
+ reraise = true
36
+ end
37
+
38
+ if reraise
39
+ raise
40
+ end
31
41
  else
32
42
  raise
33
43
  end
@@ -55,12 +55,12 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
55
55
  return nil unless @controller
56
56
 
57
57
  if @many == true
58
- controller_serializer = @controller.try(:native_serializer_plural_config)
58
+ controller_serializer = @controller.class.native_serializer_plural_config
59
59
  elsif @many == false
60
- controller_serializer = @controller.try(:native_serializer_singular_config)
60
+ controller_serializer = @controller.class.native_serializer_singular_config
61
61
  end
62
62
 
63
- return controller_serializer || @controller.try(:native_serializer_config)
63
+ return controller_serializer || @controller.class.native_serializer_config
64
64
  end
65
65
 
66
66
  # Filter a single subconfig for specific keys. By default, keys from `fields` are removed from the
@@ -114,8 +114,8 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
114
114
  def filter_from_request(cfg)
115
115
  return cfg unless @controller
116
116
 
117
- except_param = @controller.try(:native_serializer_except_query_param)
118
- only_param = @controller.try(:native_serializer_only_query_param)
117
+ except_param = @controller.class.native_serializer_except_query_param
118
+ only_param = @controller.class.native_serializer_only_query_param
119
119
  if except_param && except = @controller.request&.query_parameters&.[](except_param).presence
120
120
  if except = except.split(",").map(&:strip).map(&:to_sym).presence
121
121
  # Filter `only`, `except` (additive), `include`, `methods`, and `serializer_methods`.
@@ -160,10 +160,10 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
160
160
  def _get_associations_limit
161
161
  return @_get_associations_limit if defined?(@_get_associations_limit)
162
162
 
163
- limit = @controller&.native_serializer_associations_limit
163
+ limit = @controller&.class&.native_serializer_associations_limit
164
164
 
165
165
  # Extract the limit from the query parameters if it's set.
166
- if query_param = @controller&.native_serializer_associations_limit_query_param
166
+ if query_param = @controller&.class&.native_serializer_associations_limit_query_param
167
167
  if @controller.request.query_parameters.key?(query_param)
168
168
  query_limit = @controller.request.query_parameters[query_param].to_i
169
169
  if query_limit > 0
@@ -195,7 +195,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
195
195
  attachment_reflections = @model.attachment_reflections
196
196
 
197
197
  fields.each do |f|
198
- field_config = @controller.class.field_config_for(f)
198
+ field_config = @controller.class.field_configuration[f]
199
199
  next if field_config[:write_only]
200
200
 
201
201
  if f.in?(column_names)
@@ -226,7 +226,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
226
226
  #
227
227
  # # Even though we use a serializer method, if the count will later be added, then put
228
228
  # # this field into the includes_map.
229
- # if @controller.native_serializer_include_associations_count
229
+ # if @controller.class.native_serializer_include_associations_count
230
230
  # includes_map[f] = f.to_sym
231
231
  # end
232
232
  else
@@ -235,7 +235,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
235
235
  end
236
236
 
237
237
  # If we need to include the association count, then add it here.
238
- if @controller.native_serializer_include_associations_count
238
+ if @controller.class.native_serializer_include_associations_count
239
239
  method_name = "#{f}.count"
240
240
  serializer_methods[method_name] = method_name
241
241
  self.define_singleton_method(method_name) do |record|
@@ -246,28 +246,38 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
246
246
  includes[f] = sub_config
247
247
  includes_map[f] = f.to_sym
248
248
  end
249
- elsif ref = reflections["rich_text_#{f}"]
249
+ elsif @controller.class.enable_action_text && ref = reflections["rich_text_#{f}"]
250
250
  # ActionText Integration: Define rich text serializer method.
251
251
  includes_map[f] = :"rich_text_#{f}"
252
252
  serializer_methods[f] = f
253
253
  self.define_singleton_method(f) do |record|
254
254
  next record.send(f).to_s
255
255
  end
256
- elsif ref = attachment_reflections[f]
256
+ elsif @controller.class.enable_active_storage && ref = attachment_reflections[f]
257
257
  # ActiveStorage Integration: Define attachment serializer method.
258
258
  if ref.macro == :has_one_attached
259
259
  serializer_methods[f] = f
260
260
  includes_map[f] = {"#{f}_attachment": :blob}
261
261
  self.define_singleton_method(f) do |record|
262
262
  attached = record.send(f)
263
- next attached.attachment ? {signed_id: attached.signed_id, url: attached.url} : nil
263
+ next attached.attachment ? {
264
+ filename: attached.filename,
265
+ signed_id: attached.signed_id,
266
+ url: attached.url,
267
+ } : nil
264
268
  end
265
269
  elsif ref.macro == :has_many_attached
266
270
  serializer_methods[f] = f
267
271
  includes_map[f] = {"#{f}_attachments": :blob}
268
272
  self.define_singleton_method(f) do |record|
269
273
  # Iterating the collection yields attachment objects.
270
- next record.send(f).map { |a| {signed_id: a.signed_id, url: a.url} }
274
+ next record.send(f).map { |a|
275
+ {
276
+ filename: a.filename,
277
+ signed_id: a.signed_id,
278
+ url: a.url,
279
+ }
280
+ }
271
281
  end
272
282
  end
273
283
  elsif @model.method_defined?(f)
@@ -1,14 +1,12 @@
1
1
  module RESTFramework::Utils
2
2
  HTTP_VERB_ORDERING = %w(GET POST PUT PATCH DELETE OPTIONS HEAD)
3
3
 
4
- # Convert `extra_actions` hash to a consistent format: `{path:, methods:, kwargs:}`, and
5
- # additional metadata fields.
4
+ # Convert `extra_actions` hash to a consistent format: `{path:, methods:, kwargs:}`.
6
5
  #
7
6
  # If a controller is provided, labels will be added to any metadata fields.
8
7
  def self.parse_extra_actions(extra_actions, controller: nil)
9
8
  return (extra_actions || {}).map { |k, v|
10
9
  path = k
11
- metadata = {}
12
10
 
13
11
  # Convert structure to path/methods/kwargs.
14
12
  if v.is_a?(Hash) # Allow kwargs to be used to define path differently from the key.
@@ -19,11 +17,9 @@ module RESTFramework::Utils
19
17
  methods = v.delete(:method)
20
18
  end
21
19
 
22
- # First, remove the route metadata.
23
- metadata = v.delete(:metadata) || {}
24
-
25
- # Add label to fields.
26
- if controller && metadata[:fields]
20
+ # Add label to metadata fields, if any exist.
21
+ metadata = v[:metadata]
22
+ if controller && metadata&.key?(:fields)
27
23
  metadata[:fields] = metadata[:fields].map { |f|
28
24
  [f, {}]
29
25
  }.to_h if metadata[:fields].is_a?(Array)
@@ -56,8 +52,6 @@ module RESTFramework::Utils
56
52
  path: path,
57
53
  methods: methods,
58
54
  kwargs: kwargs,
59
- type: :extra,
60
- metadata: metadata.presence,
61
55
  }.compact,
62
56
  ]
63
57
  }.to_h
@@ -91,25 +85,28 @@ module RESTFramework::Utils
91
85
  current_levels = current_path.count("/")
92
86
  current_comparable_path = %r{^#{Regexp.quote(self.comparable_path(current_path))}(/|$)}
93
87
 
94
- # Add helpful properties of the current route.
95
- path_args = current_route.required_parts.map { |n| request.path_parameters[n] }
96
- route_props = {
97
- with_path_args: ->(r) {
98
- r.format(r.required_parts.each_with_index.map { |p, i| [p, path_args[i]] }.to_h)
99
- },
100
- }
88
+ # Get current route path parameters.
89
+ path_params = current_route.required_parts.map { |n| request.path_parameters[n] }
101
90
 
102
91
  # Return routes that match our current route subdomain/pattern, grouped by controller. We
103
92
  # precompute certain properties of the route for performance.
104
- return route_props, application_routes.routes.select { |r|
93
+ return application_routes.routes.select { |r|
105
94
  # We `select` first to avoid unnecessarily calculating metadata for routes we don't even want
106
95
  # to show.
107
96
  (r.defaults[:subdomain].blank? || r.defaults[:subdomain] == request.subdomain) &&
108
- current_comparable_path.match?(self.comparable_path(r.path.spec.to_s)) &&
109
- r.defaults[:controller].present? &&
110
- r.defaults[:action].present?
97
+ current_comparable_path.match?(self.comparable_path(r.path.spec.to_s)) &&
98
+ r.defaults[:controller].present? &&
99
+ r.defaults[:action].present?
111
100
  }.map { |r|
112
101
  path = r.path.spec.to_s.gsub("(.:format)", "")
102
+
103
+ # Starts at the number of levels in current path, and removes the `(.:format)` at the end.
104
+ relative_path = path.split("/")[current_levels..]&.join("/").presence || "/"
105
+
106
+ # This path is what would need to be concatenated onto the current path to get to the
107
+ # destination path.
108
+ concat_path = relative_path.gsub(/^[^\/]*/, "").presence || "/"
109
+
113
110
  levels = path.count("/")
114
111
  matches_path = current_path == path
115
112
  matches_params = r.required_parts.length == current_route.required_parts.length
@@ -118,8 +115,11 @@ module RESTFramework::Utils
118
115
  route: r,
119
116
  verb: r.verb,
120
117
  path: path,
121
- # Starts at the number of levels in current path, and removes the `(.:format)` at the end.
122
- relative_path: path.split("/")[current_levels..]&.join("/").presence || "/",
118
+ path_with_params: r.format(
119
+ r.required_parts.each_with_index.map { |p, i| [p, path_params[i]] }.to_h,
120
+ ),
121
+ relative_path: relative_path,
122
+ concat_path: concat_path,
123
123
  controller: r.defaults[:controller].presence,
124
124
  action: r.defaults[:action].presence,
125
125
  matches_path: matches_path,
@@ -153,16 +153,21 @@ module RESTFramework::Utils
153
153
  end
154
154
 
155
155
  # Parse fields hashes.
156
- def self.parse_fields_hash(fields_hash, model, exclude_associations: nil)
157
- parsed_fields = fields_hash[:only] || (
158
- model ? self.fields_for(model, exclude_associations: exclude_associations) : []
156
+ def self.parse_fields_hash(h, model, exclude_associations:, action_text:, active_storage:)
157
+ parsed_fields = h[:only] || (
158
+ model ? self.fields_for(
159
+ model,
160
+ exclude_associations: exclude_associations,
161
+ action_text: action_text,
162
+ active_storage: active_storage,
163
+ ) : []
159
164
  )
160
- parsed_fields += fields_hash[:include].map(&:to_s) if fields_hash[:include]
161
- parsed_fields -= fields_hash[:exclude].map(&:to_s) if fields_hash[:exclude]
162
- parsed_fields -= fields_hash[:except].map(&:to_s) if fields_hash[:except]
165
+ parsed_fields += h[:include].map(&:to_s) if h[:include]
166
+ parsed_fields -= h[:exclude].map(&:to_s) if h[:exclude]
167
+ parsed_fields -= h[:except].map(&:to_s) if h[:except]
163
168
 
164
169
  # Warn for any unknown keys.
165
- (fields_hash.keys - [:only, :except, :include, :exclude]).each do |k|
170
+ (h.keys - [:only, :except, :include, :exclude]).each do |k|
166
171
  Rails.logger.warn("RRF: Unknown key in fields hash: #{k}")
167
172
  end
168
173
 
@@ -173,16 +178,24 @@ module RESTFramework::Utils
173
178
  # Get the fields for a given model, including not just columns (which includes
174
179
  # foreign keys), but also associations. Note that we always return an array of
175
180
  # strings, not symbols.
176
- def self.fields_for(model, exclude_associations: nil)
181
+ def self.fields_for(model, exclude_associations:, action_text:, active_storage:)
177
182
  foreign_keys = model.reflect_on_all_associations(:belongs_to).map(&:foreign_key)
178
183
  base_fields = model.column_names.reject { |c| c.in?(foreign_keys) }
179
184
 
180
185
  return base_fields if exclude_associations
181
186
 
182
- # Add associations in addition to normal columns.
183
- return base_fields + model.reflections.map { |association, ref|
187
+ # ActionText Integration: Determine the normalized field names for action text attributes.
188
+ atf = action_text ? model.reflect_on_all_associations(:has_one).collect(&:name).select { |n|
189
+ n.to_s.start_with?("rich_text_")
190
+ }.map { |n| n.to_s.delete_prefix("rich_text_") } : []
191
+
192
+ # ActiveStorage Integration: Determine the normalized field names for active storage attributes.
193
+ asf = active_storage ? model.attachment_reflections.keys : []
194
+
195
+ # Associations:
196
+ associations = model.reflections.map { |association, ref|
184
197
  # Ignore associations for which we have custom integrations.
185
- if ref.class_name.in?(%w(ActiveStorage::Attachment ActiveStorage::Blob ActionText::RichText))
198
+ if ref.class_name.in?(%w(ActionText::RichText ActiveStorage::Attachment ActiveStorage::Blob))
186
199
  next nil
187
200
  end
188
201
 
@@ -193,11 +206,9 @@ module RESTFramework::Utils
193
206
  end
194
207
 
195
208
  next association
196
- }.compact + model.reflect_on_all_associations(:has_one).collect(&:name).select { |n|
197
- n.to_s.start_with?("rich_text_")
198
- }.map { |n|
199
- n.to_s.delete_prefix("rich_text_")
200
- } + model.attachment_reflections.keys
209
+ }.compact
210
+
211
+ return base_fields + associations + atf + asf
201
212
  end
202
213
 
203
214
  # Get the sub-fields that may be serialized and filtered/ordered for a reflection.
@@ -223,7 +234,7 @@ module RESTFramework::Utils
223
234
  end
224
235
 
225
236
  # Get a field's id/ids variation.
226
- def self.get_id_field(field, reflection)
237
+ def self.id_field_for(field, reflection)
227
238
  if reflection.collection?
228
239
  return "#{field.singularize}_ids"
229
240
  elsif reflection.belongs_to?
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.9.16
4
+ version: 0.11.0
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: 2024-12-21 00:00:00.000000000 Z
11
+ date: 2024-12-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -54,7 +54,7 @@ files:
54
54
  - lib/rest_framework/engine.rb
55
55
  - lib/rest_framework/errors.rb
56
56
  - lib/rest_framework/errors/base_error.rb
57
- - lib/rest_framework/errors/nil_passed_to_api_response_error.rb
57
+ - lib/rest_framework/errors/nil_passed_to_render_api_error.rb
58
58
  - lib/rest_framework/errors/unknown_model_error.rb
59
59
  - lib/rest_framework/filters.rb
60
60
  - lib/rest_framework/filters/base_filter.rb