rest_framework 1.1.0 → 2.0.0.beta1

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +107 -41
  3. data/VERSION +1 -1
  4. data/app/views/rest_framework/routes_and_forms/_html_form.html.erb +1 -1
  5. data/lib/rest_framework/controller/actions.rb +256 -0
  6. data/lib/rest_framework/controller/bulk.rb +247 -32
  7. data/lib/rest_framework/controller/crud.rb +13 -9
  8. data/lib/rest_framework/controller/openapi.rb +12 -8
  9. data/lib/rest_framework/controller.rb +275 -164
  10. data/lib/rest_framework/errors.rb +70 -3
  11. data/lib/rest_framework/filters/base_filter.rb +10 -0
  12. data/lib/rest_framework/filters/ordering_filter.rb +41 -23
  13. data/lib/rest_framework/filters/query_filter.rb +24 -5
  14. data/lib/rest_framework/filters/search_filter.rb +8 -4
  15. data/lib/rest_framework/paginators/page_number_paginator.rb +34 -19
  16. data/lib/rest_framework/routers.rb +52 -182
  17. data/lib/rest_framework/serializers/active_model_serializer_adapter_factory.rb +2 -2
  18. data/lib/rest_framework/serializers/base_serializer.rb +2 -2
  19. data/lib/rest_framework/serializers/native_serializer.rb +78 -24
  20. data/lib/rest_framework/utils.rb +39 -70
  21. data/lib/rest_framework/version.rb +8 -5
  22. data/lib/rest_framework.rb +7 -41
  23. metadata +5 -12
  24. data/lib/rest_framework/errors/base_error.rb +0 -5
  25. data/lib/rest_framework/errors/nil_passed_to_render_api_error.rb +0 -14
  26. data/lib/rest_framework/generators/controller_generator.rb +0 -64
  27. data/lib/rest_framework/generators.rb +0 -4
  28. data/lib/rest_framework/mixins/base_controller_mixin.rb +0 -12
  29. data/lib/rest_framework/mixins/bulk_model_controller_mixin.rb +0 -55
  30. data/lib/rest_framework/mixins/model_controller_mixin.rb +0 -110
  31. data/lib/rest_framework/mixins.rb +0 -7
@@ -16,7 +16,7 @@ class RESTFramework::Serializers::BaseSerializer
16
16
  end
17
17
 
18
18
  # Synonym for `serialize` for compatibility with `active_model_serializers`.
19
- # :nocov:
19
+ # simplecov:disable
20
20
  def serializable_hash(*args)
21
21
  self.serialize(*args)
22
22
  end
@@ -35,7 +35,7 @@ class RESTFramework::Serializers::BaseSerializer
35
35
  def associations(*args, **kwargs)
36
36
  []
37
37
  end
38
- # :nocov:
38
+ # simplecov:enable
39
39
  end
40
40
 
41
41
  # Alias for convenience.
@@ -28,8 +28,10 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
28
28
  # Determine model either explicitly, or by inspecting @object or @controller.
29
29
  @model = model
30
30
  @model ||= @object.class if @object.is_a?(ActiveRecord::Base)
31
- @model ||= @object[0].class if
32
- @many && @object.is_a?(Enumerable) && @object.is_a?(ActiveRecord::Base)
31
+ @model ||= @object.klass if @many && @object.is_a?(ActiveRecord::Relation)
32
+ @model ||= @object.first.class if @many &&
33
+ @object.is_a?(Enumerable) &&
34
+ @object.first.is_a?(ActiveRecord::Base)
33
35
 
34
36
  @model ||= @controller.class.model if @controller
35
37
  end
@@ -109,25 +111,77 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
109
111
  controller_serializer || @controller.class.native_serializer_config
110
112
  end
111
113
 
112
- # Get the associations limit from the controller.
113
- def _associations_limit
114
- return @_associations_limit if defined?(@_associations_limit)
114
+ # The record cap for a collection association (`nil` = unlimited). The default is applied even
115
+ # when the feature is off, so responses are always bounded. `key?` (not `||`) reads the
116
+ # `field_config` override so an explicit `nil` there means unlimited/uncapped rather than falling
117
+ # back to the controller default.
118
+ def _effective_association_limit(association_name, field_config)
119
+ controller = @controller&.class
115
120
 
116
- limit = @controller&.class&.native_serializer_associations_limit
121
+ default = field_config.key?(:limit) ?
122
+ field_config[:limit] : controller&.association_limit
123
+ return default unless controller&.enable_association_queries
117
124
 
118
- # Extract the limit from the query parameters if it's set.
119
- if query_param = @controller&.class&.native_serializer_associations_limit_query_param
120
- if @controller.request.query_parameters.key?(query_param)
121
- query_limit = @controller.request.query_parameters[query_param].to_i
122
- if query_limit > 0
123
- limit = query_limit
124
- else
125
- limit = nil
126
- end
127
- end
128
- end
125
+ requested = self._requested_association_limit(association_name)
126
+ return default if requested.nil?
127
+
128
+ max = field_config.key?(:limit_max) ?
129
+ field_config[:limit_max] : controller.association_limit_max
130
+
131
+ # `all` means "as many as allowed" — the cap, or unlimited when the cap is `nil`.
132
+ return max if requested == :all
133
+
134
+ max ? [ requested, max ].min : requested
135
+ end
136
+
137
+ # `all`, `none`, and `0` all mean "no limit" — `0` is free to reuse as a sentinel since an
138
+ # association is dropped via `except`, not `limit=0`. Anything else must be a plain positive
139
+ # integer, so a nested/array param or junk can't drive the query.
140
+ def _requested_association_limit(association_name)
141
+ return nil unless prefix = @controller.class.association_query_prefix.presence
142
+
143
+ raw = @controller.request&.query_parameters&.[]("#{prefix}.#{association_name}.limit")
144
+ return nil unless raw.is_a?(String)
145
+
146
+ raw = raw.strip
147
+ return :all if raw.in?(%w[all none])
148
+ return nil unless raw.match?(/\A\d+\z/)
149
+
150
+ # The regex guarantees a non-negative integer, so anything but zero is a positive limit.
151
+ limit = raw.to_i
152
+ limit.zero? ? :all : limit
153
+ end
154
+
155
+ # The fields to serialize for association `f`, honoring a consumer's `?<prefix>.<f>.fields=`
156
+ # request bounded by the allowlist. Only active when the feature is enabled.
157
+ def _effective_association_fields(association_name, ref, field_config)
158
+ default_fields = field_config[:fields]
159
+ return default_fields unless @controller&.class&.enable_association_queries
160
+ return default_fields if ref.polymorphic? # no single target class to bound the request
161
+
162
+ requested = self._requested_association_fields(association_name)
163
+ return default_fields if requested.blank?
164
+
165
+ # The primary key is always kept so records stay identifiable. `_valid_association_field?` is
166
+ # the last line of defense against leaking a non-serializable or nested-association field.
167
+ allowed = (default_fields + (field_config[:requestable_fields] || [])).uniq
168
+ valid = (requested & allowed).select { |sf| self._valid_association_field?(ref, sf) }
169
+ (Array(ref.klass.primary_key).map(&:to_s) + valid).uniq
170
+ end
171
+
172
+ # Only a plain scalar string is honored, so a nested-hash/array param can't reach `where`/`split`.
173
+ def _requested_association_fields(association_name)
174
+ return nil unless prefix = @controller.class.association_query_prefix.presence
175
+
176
+ raw = @controller.request&.query_parameters&.[]("#{prefix}.#{association_name}.fields")
177
+ return nil unless raw.is_a?(String)
178
+
179
+ raw.split(",").map { |x| x.strip.presence }.compact
180
+ end
129
181
 
130
- @_associations_limit = limit
182
+ def _valid_association_field?(ref, field)
183
+ field.in?(ref.klass.column_names) ||
184
+ (ref.klass.method_defined?(field) && !ref.klass.reflect_on_association(field.to_sym))
131
185
  end
132
186
 
133
187
  # Get a serializer configuration from the controller. `@controller` and `@model` must be set.
@@ -156,7 +210,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
156
210
  elsif ref = reflections[f]
157
211
  sub_columns = []
158
212
  sub_methods = []
159
- field_config[:sub_fields].each do |sf|
213
+ self._effective_association_fields(f, ref, field_config).each do |sf|
160
214
  if !ref.polymorphic? && sf.in?(ref.klass.column_names)
161
215
  sub_columns << sf
162
216
  else
@@ -167,9 +221,9 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
167
221
 
168
222
  # Apply certain rules regarding collection associations.
169
223
  if ref.collection?
170
- # If we need to limit the number of serialized association records, then dynamically add a
171
- # serializer method to do so.
172
- if limit = self._associations_limit
224
+ # A finite limit needs a per-record `.limit` query, since eager `includes` can't cap rows
225
+ # per parent; an unlimited (`nil`) association falls through to `includes` preloading.
226
+ if limit = self._effective_association_limit(f, field_config)
173
227
  serializer_methods[f] = f
174
228
  self.define_singleton_method(f) do |record|
175
229
  next record.send(f).limit(limit).as_json(**sub_config)
@@ -179,7 +233,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
179
233
  #
180
234
  # # Even though we use a serializer method, if the count will later be added, then put
181
235
  # # this field into the includes_map.
182
- # if @controller.class.native_serializer_include_associations_count
236
+ # if @controller.class.include_association_count
183
237
  # includes_map[f] = f.to_sym
184
238
  # end
185
239
  else
@@ -188,7 +242,7 @@ class RESTFramework::Serializers::NativeSerializer < RESTFramework::Serializers:
188
242
  end
189
243
 
190
244
  # If we need to include the association count, then add it here.
191
- if @controller.class.native_serializer_include_associations_count
245
+ if @controller.class.include_association_count
192
246
  method_name = "#{f}.count"
193
247
  serializer_methods[method_name] = method_name
194
248
  self.define_singleton_method(method_name) do |record|
@@ -1,61 +1,15 @@
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:, metadata:, kwargs:}`.
5
- def self.parse_extra_actions(extra_actions)
6
- (extra_actions || {}).map { |k, v|
7
- path = k
8
- kwargs = {}
9
-
10
- # Convert structure to path/methods/kwargs.
11
- if v.is_a?(Hash)
12
- # Symbolize keys (which also makes a copy so we don't mutate the original).
13
- v = v.symbolize_keys
14
-
15
- # Cast method/methods to an array.
16
- methods = [ v.delete(:methods), v.delete(:method) ].flatten.compact
17
-
18
- # Override path if it's provided.
19
- if v.key?(:path)
20
- path = v.delete(:path)
21
- end
22
-
23
- # Extract metadata, if provided.
24
- metadata = v.delete(:metadata).presence
25
-
26
- # Pass any further kwargs to the underlying Rails interface.
27
- kwargs = v
28
- else
29
- methods = [ v ].flatten
30
- end
31
-
32
- next [
33
- k,
34
- {
35
- path: path,
36
- methods: methods,
37
- metadata: metadata,
38
- kwargs: kwargs,
39
- }.compact,
40
- ]
41
- }.to_h
42
- end
43
-
44
- def self.get_skipped_builtin_actions(controller_class, singular)
45
- candidates = (
46
- RESTFramework::BUILTIN_ACTIONS.keys - (singular ? [ :index ] : [])
47
- ) + RESTFramework::BUILTIN_MEMBER_ACTIONS.keys
48
-
49
- return candidates unless controller_class.model
50
-
51
- exclude = controller_class.excluded_actions&.to_set || Set.new
52
- candidates.reject do |action|
53
- controller_class.method_defined?(action) && !exclude.include?(action)
54
- end
55
- end
56
-
57
4
  # Get the first route pattern which matches the given request.
58
5
  def self.get_request_route(application_routes, request)
6
+ # Prefer the route already resolved by the router to avoid an expensive `recognize` call. This
7
+ # is also required for Rails 8.1+ where OPTIONS routes are non-anchored, causing `path_info` to
8
+ # be modified during dispatch, which makes `recognize` fail from inside the controller action.
9
+ if route = request.env["action_dispatch.route"]
10
+ return route
11
+ end
12
+
59
13
  application_routes.router.recognize(request) { |route, _| return route }
60
14
  end
61
15
 
@@ -165,9 +119,8 @@ module RESTFramework::Utils
165
119
  parsed_fields.map(&:to_s)
166
120
  end
167
121
 
168
- # Get the fields for a given model, including not just columns (which includes
169
- # foreign keys), but also associations. Note that we always return an array of
170
- # strings, not symbols.
122
+ # Get the fields for a given model, including not just columns (which includes foreign keys), but
123
+ # also associations. Note that we always return an array of strings, not symbols.
171
124
  def self.fields_for(model, exclude_associations:, action_text:, active_storage:)
172
125
  foreign_keys = model.reflect_on_all_associations(:belongs_to).map(&:foreign_key)
173
126
  base_fields = model.column_names.reject { |c| c.in?(foreign_keys) }
@@ -201,23 +154,23 @@ module RESTFramework::Utils
201
154
  base_fields + associations + atf + asf
202
155
  end
203
156
 
204
- # Get the sub-fields that may be serialized and filtered/ordered for a reflection.
205
- def self.sub_fields_for(ref)
157
+ # Get the association's fields that may be serialized and filtered/ordered for a reflection.
158
+ def self.association_fields_for(ref)
206
159
  if !ref.polymorphic? && model = ref.klass
207
- sub_fields = [ model.primary_key ].flatten.compact
160
+ fields = [ model.primary_key ].flatten.compact
208
161
  label_fields = RESTFramework.config.label_fields
209
162
 
210
163
  # Preferably find a database column to use as label.
211
164
  if match = label_fields.find { |f| f.in?(model.column_names) }
212
- return sub_fields + [ match ]
165
+ return fields + [ match ]
213
166
  end
214
167
 
215
168
  # Otherwise, find a method.
216
169
  if match = label_fields.find { |f| model.method_defined?(f) }
217
- return sub_fields + [ match ]
170
+ return fields + [ match ]
218
171
  end
219
172
 
220
- return sub_fields
173
+ return fields
221
174
  end
222
175
 
223
176
  [ "id", "name" ]
@@ -236,6 +189,30 @@ module RESTFramework::Utils
236
189
  nil
237
190
  end
238
191
 
192
+ # Find the REST controller for `model` at the same namespace level as `current_controller`, e.g.
193
+ # `Api::Demo::MoviesController` + `Genre` => `Api::Demo::GenresController`, or `nil` if none. The
194
+ # `model` match guards against trusting a same-named controller for a different model.
195
+ def self.controller_for_model(current_controller, model)
196
+ return nil unless model && (base_name = current_controller.name)
197
+
198
+ namespace = base_name.deconstantize
199
+ model_name = model.model_name
200
+
201
+ # Plural for a collection controller, singular for a singular-resource one.
202
+ [ model_name.plural, model_name.singular ].each do |name|
203
+ candidate_name = "#{name.camelize}Controller"
204
+ candidate_name = "#{namespace}::#{candidate_name}" if namespace.present?
205
+
206
+ candidate = candidate_name.safe_constantize
207
+ next unless candidate.is_a?(Class) && candidate.include?(RESTFramework::Controller)
208
+ next unless candidate.model == model
209
+
210
+ return candidate
211
+ end
212
+
213
+ nil
214
+ end
215
+
239
216
  # Wrap a serializer with an adapter if it is an ActiveModel::Serializer.
240
217
  def self.wrap_ams(s)
241
218
  if defined?(ActiveModel::Serializer) && (s < ActiveModel::Serializer)
@@ -244,12 +221,4 @@ module RESTFramework::Utils
244
221
 
245
222
  s
246
223
  end
247
-
248
- # Used for deprecated mixins that rely on model being determined from the controller name.
249
- def self.get_model(controller_class)
250
- begin
251
- controller_class.name.demodulize.chomp("Controller").singularize.constantize
252
- rescue NameError
253
- end
254
- end
255
224
  end
@@ -22,11 +22,14 @@ module RESTFramework
22
22
  UNKNOWN
23
23
  end
24
24
 
25
- def self.stamp_version
26
- # Only stamp the version if it's not unknown.
27
- if RESTFramework::VERSION != UNKNOWN
28
- File.write(VERSION_FILEPATH, RESTFramework::VERSION)
25
+ def self.stamp_version(version = nil)
26
+ # Stamp the given version into the VERSION file, deriving it from git when none is provided.
27
+ # Returns the stamped version so callers don't rely on the (require-time) `VERSION` constant.
28
+ version ||= self.get_version
29
+ if version != UNKNOWN
30
+ File.write(VERSION_FILEPATH, version)
29
31
  end
32
+ version
30
33
  end
31
34
 
32
35
  def self.unstamp_version
@@ -34,5 +37,5 @@ module RESTFramework
34
37
  end
35
38
  end
36
39
 
37
- VERSION = Version.get_version
40
+ VERSION = Version.get_version(skip_git: true)
38
41
  end
@@ -1,26 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RESTFramework
4
- BUILTIN_FORM_ACTIONS = [ :new, :edit ].freeze
5
- BUILTIN_ACTIONS = {
6
- index: :get,
7
- new: :get,
8
- create: :post,
9
- }.freeze
10
- BUILTIN_MEMBER_ACTIONS = {
11
- show: :get,
12
- edit: :get,
13
- update: [ :put, :patch ].freeze,
14
- destroy: :delete,
15
- }.freeze
16
- RRF_BUILTIN_ACTIONS = {
17
- options: :options,
18
- }.freeze
19
- RRF_BUILTIN_BULK_ACTIONS = {
20
- update_all: [ :put, :patch ].freeze,
21
- destroy_all: :delete,
22
- }.freeze
23
-
24
4
  # Storage for extra routes and associated metadata.
25
5
  EXTRA_ACTION_ROUTES = Set.new
26
6
  ROUTE_METADATA = {}
@@ -47,6 +27,7 @@ module RESTFramework
47
27
  # Bootstrap Icons
48
28
  "bootstrap-icons.min.css" => {
49
29
  url: "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css",
30
+ sri: "sha384-XGjxtQfXaH2tnPFa9x+ruJTuLE3Aa6LhHSWRr1XeTyhezb4abCG4ccI5AkVDxqC+",
50
31
  inline_fonts: true,
51
32
  },
52
33
 
@@ -74,19 +55,23 @@ module RESTFramework
74
55
  extra_tag_attrs: { class: "rrf-light-mode" },
75
56
  },
76
57
 
77
- # NeatJSON
58
+ # NeatJSON. The package publishes no minified build; jsdelivr's `neatjson.min.js` is synthesized
59
+ # on the fly (so its bytes/SRI aren't stable), so we pin the published `neatjson.js` instead.
78
60
  "neatjson.min.js" => {
79
- url: "https://cdn.jsdelivr.net/npm/neatjson@0.10.6/javascript/neatjson.min.js",
61
+ url: "https://cdn.jsdelivr.net/npm/neatjson@0.10.6/javascript/neatjson.js",
62
+ sri: "sha384-7yRMvyGBuBnnlsgFwUDx7wY7Y/kXt7Irg5xppXVAI0yp3R0870lWaXNP/JE2rbFA",
80
63
  exclude_from_docs: true,
81
64
  },
82
65
 
83
66
  # Trix
84
67
  "trix.min.css" => {
85
68
  url: "https://unpkg.com/trix@2.0.8/dist/trix.css",
69
+ sri: "sha384-Cgg84c/W0Q5VrTzc4ITmV88Ocx4Pn1YlTXPvSTJjX6+lxJRLYrZ4chaighxlqOY1",
86
70
  exclude_from_docs: true,
87
71
  },
88
72
  "trix.min.js" => {
89
73
  url: "https://unpkg.com/trix@2.0.8/dist/trix.umd.min.js",
74
+ sri: "sha384-Ki3zDe3whjAHK/GOksrYLGCU8m0SkmiJQ4Kqm3jQlM3YCl2hFBf17s9bzjVYmXWX",
90
75
  exclude_from_docs: true,
91
76
  },
92
77
  }.map { |name, cfg|
@@ -149,19 +134,6 @@ module RESTFramework
149
134
  # Permits use of `render(api: obj)` syntax over `render_api(obj)`; `true` by default.
150
135
  attr_accessor :register_api_renderer
151
136
 
152
- # Run `rrf_finalize` on controllers automatically using a `TracePoint` hook. This is `true` by
153
- # default, and can be disabled for performance, and must be global because we have to determine
154
- # this before any controller-specific configuration is set. If this is set to `false`, then you
155
- # must manually call `rrf_finalize` after any configuration on each controller that needs to
156
- # participate in:
157
- # - Model delegation, for the helper methods to be defined dynamically.
158
- # - Websockets, for `::Channel` class to be defined dynamically.
159
- # - Controller configuration freezing.
160
- attr_accessor :auto_finalize
161
-
162
- # Freeze configuration attributes during finalization to prevent accidental mutation.
163
- attr_accessor :freeze_config
164
-
165
137
  # Specify reverse association tables that are typically very large, and therefore should not be
166
138
  # added to fields by default.
167
139
  attr_accessor :large_reverse_association_tables
@@ -169,9 +141,6 @@ module RESTFramework
169
141
  # Whether the backtrace should be shown in rescued errors.
170
142
  attr_accessor :show_backtrace
171
143
 
172
- # Disable `rescue_from` on the controller mixins.
173
- attr_accessor :disable_rescue_from
174
-
175
144
  # The default label fields to use when generating labels for `has_many` associations.
176
145
  attr_accessor :label_fields
177
146
 
@@ -191,7 +160,6 @@ module RESTFramework
191
160
 
192
161
  def initialize
193
162
  self.register_api_renderer = true
194
- self.auto_finalize = true
195
163
 
196
164
  self.show_backtrace = Rails.env.development?
197
165
 
@@ -223,8 +191,6 @@ end
223
191
  require_relative "rest_framework/engine"
224
192
  require_relative "rest_framework/errors"
225
193
  require_relative "rest_framework/filters"
226
- require_relative "rest_framework/generators"
227
- require_relative "rest_framework/mixins"
228
194
  require_relative "rest_framework/paginators"
229
195
  require_relative "rest_framework/routers"
230
196
  require_relative "rest_framework/serializers"
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: 1.1.0
4
+ version: 2.0.0.beta1
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: 2026-04-16 00:00:00.000000000 Z
11
+ date: 2026-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -55,25 +55,18 @@ files:
55
55
  - app/views/rest_framework/routes_and_forms/routes/_route.html.erb
56
56
  - lib/rest_framework.rb
57
57
  - lib/rest_framework/controller.rb
58
+ - lib/rest_framework/controller/actions.rb
58
59
  - lib/rest_framework/controller/bulk.rb
59
60
  - lib/rest_framework/controller/crud.rb
60
61
  - lib/rest_framework/controller/openapi.rb
61
62
  - lib/rest_framework/engine.rb
62
63
  - lib/rest_framework/errors.rb
63
- - lib/rest_framework/errors/base_error.rb
64
- - lib/rest_framework/errors/nil_passed_to_render_api_error.rb
65
64
  - lib/rest_framework/filters.rb
66
65
  - lib/rest_framework/filters/base_filter.rb
67
66
  - lib/rest_framework/filters/ordering_filter.rb
68
67
  - lib/rest_framework/filters/query_filter.rb
69
68
  - lib/rest_framework/filters/ransack_filter.rb
70
69
  - lib/rest_framework/filters/search_filter.rb
71
- - lib/rest_framework/generators.rb
72
- - lib/rest_framework/generators/controller_generator.rb
73
- - lib/rest_framework/mixins.rb
74
- - lib/rest_framework/mixins/base_controller_mixin.rb
75
- - lib/rest_framework/mixins/bulk_model_controller_mixin.rb
76
- - lib/rest_framework/mixins/model_controller_mixin.rb
77
70
  - lib/rest_framework/paginators.rb
78
71
  - lib/rest_framework/paginators/base_paginator.rb
79
72
  - lib/rest_framework/paginators/page_number_paginator.rb
@@ -105,9 +98,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
105
98
  version: 2.7.5
106
99
  required_rubygems_version: !ruby/object:Gem::Requirement
107
100
  requirements:
108
- - - ">="
101
+ - - ">"
109
102
  - !ruby/object:Gem::Version
110
- version: '0'
103
+ version: 1.3.1
111
104
  requirements: []
112
105
  rubygems_version: 3.4.10
113
106
  signing_key:
@@ -1,5 +0,0 @@
1
- class RESTFramework::Errors::BaseError < StandardError
2
- end
3
-
4
- # Alias for convenience.
5
- RESTFramework::BaseError = RESTFramework::Errors::BaseError
@@ -1,14 +0,0 @@
1
- class RESTFramework::Errors::NilPassedToRenderAPIError < RESTFramework::Errors::BaseError
2
- def message
3
- <<~MSG.split("\n").join(" ")
4
- Payload of `nil` was passed to `render_api`; this is unsupported. If you want a blank
5
- response, pass `''` (an empty string) as the payload. If this was the result of a `find_by`
6
- (or similar Active Record method) not finding a record, you should use the bang version (e.g.,
7
- `find_by!`) to raise `ActiveRecord::RecordNotFound`, which the REST controller will catch and
8
- return an appropriate error response.
9
- MSG
10
- end
11
- end
12
-
13
- # Alias for convenience.
14
- RESTFramework::NilPassedToRenderAPIError = RESTFramework::Errors::NilPassedToRenderAPIError
@@ -1,64 +0,0 @@
1
- require "rails/generators"
2
-
3
- # Most projects don't have the inflection "REST" as an acronym, so this is a helper class to prevent
4
- # this generator from being namespaced as `"r_e_s_t_framework"`.
5
- # :nocov:
6
- class RESTFrameworkCustomGeneratorControllerNamespace < String
7
- def camelize
8
- "RESTFramework"
9
- end
10
- end
11
- # :nocov:
12
-
13
- class RESTFramework::Generators::ControllerGenerator < Rails::Generators::Base
14
- PATH_REGEX = %r{^[a-z0-9][a-z0-9_/]+$}
15
-
16
- desc <<~END
17
- Description:
18
- Generates a new REST Framework controller.
19
-
20
- Specify the controller as a path, including the module, if needed, like:
21
- 'parent_module/controller_name'.
22
-
23
- Example:
24
- `rails generate rest_framework:controller user_api/groups`
25
-
26
- Generates a controller at `app/controllers/user_api/groups_controller.rb` named
27
- `UserApi::GroupsController`.
28
- END
29
-
30
- argument :path, type: :string
31
- class_option(
32
- :parent_class, type: :string, default: "ApplicationController", desc: "Inheritance parent"
33
- )
34
- class_option(
35
- :include_base,
36
- type: :boolean,
37
- default: false,
38
- desc: "Include `BaseControllerMixin`, not `ModelControllerMixin`",
39
- )
40
-
41
- # Some projects may not have the inflection "REST" as an acronym, which changes this generator to
42
- # be namespaced in `r_e_s_t_framework`, which is weird.
43
- def self.namespace
44
- RESTFrameworkCustomGeneratorControllerNamespace.new("rest_framework:controller")
45
- end
46
-
47
- def create_rest_controller_file
48
- unless PATH_REGEX.match?(self.path)
49
- raise StandardError, "Path isn't valid."
50
- end
51
-
52
- # Remove '_controller' from end of path, if it exists.
53
- cleaned_path = self.path.delete_suffix("_controller")
54
-
55
- content = <<~END
56
- class #{cleaned_path.camelize}Controller < #{options[:parent_class]}
57
- include RESTFramework::#{
58
- options[:include_base] ? "BaseControllerMixin" : "ModelControllerMixin"
59
- }
60
- end
61
- END
62
- create_file("app/controllers/#{cleaned_path}_controller.rb", content)
63
- end
64
- end
@@ -1,4 +0,0 @@
1
- module RESTFramework::Generators
2
- end
3
-
4
- require_relative "generators/controller_generator"
@@ -1,12 +0,0 @@
1
- module RESTFramework::Mixins::BaseControllerMixin
2
- def self.included(base)
3
- RESTFramework.deprecator.warn(
4
- "BaseControllerMixin is deprecated; use RESTFramework::Controller instead.",
5
- )
6
-
7
- base.include(RESTFramework::Controller)
8
- end
9
- end
10
-
11
- # Alias for convenience.
12
- RESTFramework::BaseControllerMixin = RESTFramework::Mixins::BaseControllerMixin
@@ -1,55 +0,0 @@
1
- module RESTFramework::Mixins::BulkCreateModelMixin
2
- def self.included(base)
3
- RESTFramework.deprecator.warn(<<~TXT).squish
4
- BulkCreateModelMixin is deprecated; set the `bulk = true` class attribute instead.
5
- TXT
6
-
7
- base.bulk = true
8
- end
9
- end
10
-
11
- # Mixin for updating records in bulk.
12
- module RESTFramework::Mixins::BulkUpdateModelMixin
13
- def self.included(base)
14
- RESTFramework.deprecator.warn(<<~TXT).squish
15
- BulkUpdateModelMixin is deprecated; set the `bulk = true`, and `excluded_actions` class
16
- attributes instead.
17
- TXT
18
-
19
- base.bulk = true
20
- base.excluded_actions = (base.excluded_actions - [ :update_all ]).freeze
21
- end
22
- end
23
-
24
- # Mixin for destroying records in bulk.
25
- module RESTFramework::Mixins::BulkDestroyModelMixin
26
- def self.included(base)
27
- RESTFramework.deprecator.warn(<<~TXT).squish
28
- BulkDestroyModelMixin is deprecated; set the `bulk = true`, and `excluded_actions` class
29
- attributes instead.
30
- TXT
31
-
32
- base.bulk = true
33
- base.excluded_actions = (base.excluded_actions - [ :destroy_all ]).freeze
34
- end
35
- end
36
-
37
- # Mixin that includes all the CRUD bulk mixins.
38
- module RESTFramework::Mixins::BulkModelControllerMixin
39
- def self.included(base)
40
- RESTFramework.deprecator.warn(<<~TXT).squish
41
- BulkModelControllerMixin is deprecated; use RESTFramework::Controller and set the `model` and
42
- `bulk = true` class attributes instead.
43
- TXT
44
-
45
- base.include(RESTFramework::Controller)
46
- base.model = RESTFramework::Utils.get_model(base)
47
- base.bulk = true
48
- end
49
- end
50
-
51
- # Aliases for convenience.
52
- RESTFramework::BulkCreateModelMixin = RESTFramework::Mixins::BulkCreateModelMixin
53
- RESTFramework::BulkUpdateModelMixin = RESTFramework::Mixins::BulkUpdateModelMixin
54
- RESTFramework::BulkDestroyModelMixin = RESTFramework::Mixins::BulkDestroyModelMixin
55
- RESTFramework::BulkModelControllerMixin = RESTFramework::Mixins::BulkModelControllerMixin