rest_framework 0.4.1 → 0.5.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e654cef83c3cf4505697e0417d637489a248b691ca157100b6a12dc3e0f3f9e
4
- data.tar.gz: b656446d2fd724e2b91708e4df6248cd5e6676a311e8cd23d76f07951758bffd
3
+ metadata.gz: 6e9dae13c73c724a592cf7f9f99347eae18dcc4acd135e98bcaf5f8682ea01e3
4
+ data.tar.gz: 52b5a7b3a0b3339987516e6ec2083c087b9387ceb13a2951523c339ee1509b2c
5
5
  SHA512:
6
- metadata.gz: 3a9ee1d172c5e33813352b2190f2f38540226d93d84bf77cca34fab326231f6215a702c66e3965f5239d652e35eeb36a7286210826608e11ab9a35c8edf65704
7
- data.tar.gz: e132e15651e2c9fd0cd9d1338ec2c5a795d369a6a001c2e4a957c7912dfd5539ea940945ea48c011e1c4be6e3b1feaf55b63934fdff5608135c2ac089fd83486
6
+ metadata.gz: 8c9d79bd771ab07ffbd25edcc9970a3cc4ab39dfe730330e4fd704737773f7381bc75fd3f6169a9a6c17653cd3853da45a56a46907b05be668b63f8e921ee482
7
+ data.tar.gz: 9e5ec3f519a35f02f49d2fde38f3f793649361927e437bc243ff7eba5a38a8315c4557b001a4eb305aeec04e99fb91ea00727aa910e5f33496ea58d29ad28598
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2021 Gregory N. Schmit
3
+ Copyright (c) 2022 Gregory N. Schmit
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.1
1
+ 0.5.2
@@ -1,7 +1,6 @@
1
- require_relative '../errors'
2
- require_relative '../serializers'
3
- require_relative '../utils'
4
-
1
+ require_relative "../errors"
2
+ require_relative "../serializers"
3
+ require_relative "../utils"
5
4
 
6
5
  # This module provides the common functionality for any controller mixins, a `root` action, and
7
6
  # the ability to route arbitrary actions with `extra_actions`. This is also where `api_response`
@@ -30,8 +29,8 @@ module RESTFramework::BaseControllerMixin
30
29
  end
31
30
 
32
31
  def self.included(base)
33
- if base.is_a? Class
34
- base.extend ClassMethods
32
+ if base.is_a?(Class)
33
+ base.extend(ClassMethods)
35
34
 
36
35
  # Add class attributes (with defaults) unless they already exist.
37
36
  {
@@ -43,8 +42,8 @@ module RESTFramework::BaseControllerMixin
43
42
  filter_backends: nil,
44
43
  paginator_class: nil,
45
44
  page_size: 20,
46
- page_query_param: 'page',
47
- page_size_query_param: 'page_size',
45
+ page_query_param: "page",
46
+ page_size_query_param: "page_size",
48
47
  max_page_size: nil,
49
48
  serializer_class: nil,
50
49
  serialize_to_json: true,
@@ -52,13 +51,13 @@ module RESTFramework::BaseControllerMixin
52
51
  singleton_controller: nil,
53
52
  skip_actions: nil,
54
53
  }.each do |a, default|
55
- unless base.respond_to?(a)
56
- base.class_attribute(a)
54
+ next if base.respond_to?(a)
57
55
 
58
- # Set default manually so we can still support Rails 4. Maybe later we can use the default
59
- # parameter on `class_attribute`.
60
- base.send(:"#{a}=", default)
61
- end
56
+ base.class_attribute(a)
57
+
58
+ # Set default manually so we can still support Rails 4. Maybe later we can use the default
59
+ # parameter on `class_attribute`.
60
+ base.send(:"#{a}=", default)
62
61
  end
63
62
 
64
63
  # Alias `extra_actions` to `extra_collection_actions`.
@@ -68,7 +67,11 @@ module RESTFramework::BaseControllerMixin
68
67
  end
69
68
 
70
69
  # Skip csrf since this is an API.
71
- base.skip_before_action(:verify_authenticity_token) rescue nil
70
+ begin
71
+ base.skip_before_action(:verify_authenticity_token)
72
+ rescue
73
+ nil
74
+ end
72
75
 
73
76
  # Handle some common exceptions.
74
77
  base.rescue_from(ActiveRecord::RecordNotFound, with: :record_not_found)
@@ -82,7 +85,14 @@ module RESTFramework::BaseControllerMixin
82
85
 
83
86
  # Helper to get the configured serializer class.
84
87
  def get_serializer_class
85
- return self.class.serializer_class
88
+ return nil unless serializer_class = self.class.serializer_class
89
+
90
+ # Wrap it with an adapter if it's an active_model_serializer.
91
+ if defined?(ActiveModel::Serializer) && (serializer_class < ActiveModel::Serializer)
92
+ serializer_class = RESTFramework::ActiveModelSerializerAdapterFactory.for(serializer_class)
93
+ end
94
+
95
+ return serializer_class
86
96
  end
87
97
 
88
98
  # Helper to get filtering backends, defaulting to no backends.
@@ -101,9 +111,9 @@ module RESTFramework::BaseControllerMixin
101
111
  end
102
112
 
103
113
  def record_invalid(e)
104
- return api_response({
105
- message: "Record invalid.", exception: e, errors: e.record&.errors
106
- }, status: 400)
114
+ return api_response(
115
+ {message: "Record invalid.", exception: e, errors: e.record&.errors}, status: 400
116
+ )
107
117
  end
108
118
 
109
119
  def record_not_found(e)
@@ -111,15 +121,15 @@ module RESTFramework::BaseControllerMixin
111
121
  end
112
122
 
113
123
  def record_not_saved(e)
114
- return api_response({
115
- message: "Record not saved.", exception: e, errors: e.record&.errors
116
- }, status: 406)
124
+ return api_response(
125
+ {message: "Record not saved.", exception: e, errors: e.record&.errors}, status: 406
126
+ )
117
127
  end
118
128
 
119
129
  def record_not_destroyed(e)
120
- return api_response({
121
- message: "Record not destroyed.", exception: e, errors: e.record&.errors
122
- }, status: 406)
130
+ return api_response(
131
+ {message: "Record not destroyed.", exception: e, errors: e.record&.errors}, status: 406
132
+ )
123
133
  end
124
134
 
125
135
  # Helper to render a browsable API for `html` format, along with basic `json`/`xml` formats, and
@@ -138,38 +148,38 @@ module RESTFramework::BaseControllerMixin
138
148
  end
139
149
 
140
150
  respond_to do |format|
141
- if payload == ''
142
- format.json {head :no_content} if self.serialize_to_json
143
- format.xml {head :no_content} if self.serialize_to_xml
151
+ if payload == ""
152
+ format.json { head(:no_content) } if self.class.serialize_to_json
153
+ format.xml { head(:no_content) } if self.class.serialize_to_xml
144
154
  else
145
155
  format.json {
146
156
  jkwargs = kwargs.merge(json_kwargs)
147
157
  render(json: payload, layout: false, **jkwargs)
148
- } if self.serialize_to_json
158
+ } if self.class.serialize_to_json
149
159
  format.xml {
150
160
  xkwargs = kwargs.merge(xml_kwargs)
151
161
  render(xml: payload, layout: false, **xkwargs)
152
- } if self.serialize_to_xml
162
+ } if self.class.serialize_to_xml
153
163
  # TODO: possibly support more formats here if supported?
154
164
  end
155
165
  format.html {
156
166
  @payload = payload
157
- if payload == ''
158
- @json_payload = '' if self.serialize_to_json
159
- @xml_payload = '' if self.serialize_to_xml
167
+ if payload == ""
168
+ @json_payload = "" if self.class.serialize_to_json
169
+ @xml_payload = "" if self.class.serialize_to_xml
160
170
  else
161
- @json_payload = payload.to_json if self.serialize_to_json
162
- @xml_payload = payload.to_xml if self.serialize_to_xml
171
+ @json_payload = payload.to_json if self.class.serialize_to_json
172
+ @xml_payload = payload.to_xml if self.class.serialize_to_xml
163
173
  end
164
174
  @template_logo_text ||= "Rails REST Framework"
165
175
  @title ||= self.controller_name.camelize
166
- @route_groups ||= RESTFramework::Utils::get_routes(Rails.application.routes, request)
176
+ @route_groups ||= RESTFramework::Utils.get_routes(Rails.application.routes, request)
167
177
  hkwargs = kwargs.merge(html_kwargs)
168
178
  begin
169
179
  render(**hkwargs)
170
180
  rescue ActionView::MissingTemplate # fallback to rest_framework layout
171
181
  hkwargs[:layout] = "rest_framework"
172
- hkwargs[:html] = ''
182
+ hkwargs[:html] = ""
173
183
  render(**hkwargs)
174
184
  end
175
185
  }
@@ -1,13 +1,12 @@
1
- require_relative 'base'
2
- require_relative '../filters'
3
-
1
+ require_relative "base"
2
+ require_relative "../filters"
4
3
 
5
4
  # This module provides the core functionality for controllers based on models.
6
5
  module RESTFramework::BaseModelControllerMixin
7
6
  include RESTFramework::BaseControllerMixin
8
7
 
9
8
  def self.included(base)
10
- if base.is_a? Class
9
+ if base.is_a?(Class)
11
10
  RESTFramework::BaseControllerMixin.included(base)
12
11
 
13
12
  # Add class attributes (with defaults) unless they already exist.
@@ -22,7 +21,7 @@ module RESTFramework::BaseModelControllerMixin
22
21
 
23
22
  # Attributes for finding records.
24
23
  find_by_fields: nil,
25
- find_by_query_param: 'find_by',
24
+ find_by_query_param: "find_by",
26
25
 
27
26
  # Attributes for create/update parameters.
28
27
  allowed_parameters: nil,
@@ -32,27 +31,28 @@ module RESTFramework::BaseModelControllerMixin
32
31
  native_serializer_config: nil,
33
32
  native_serializer_singular_config: nil,
34
33
  native_serializer_plural_config: nil,
34
+ native_serializer_except_query_param: "except",
35
35
 
36
36
  # Attributes for default model filtering (and ordering).
37
37
  filterset_fields: nil,
38
38
  ordering_fields: nil,
39
- ordering_query_param: 'ordering',
39
+ ordering_query_param: "ordering",
40
40
  ordering_no_reorder: false,
41
41
  search_fields: nil,
42
- search_query_param: 'search',
42
+ search_query_param: "search",
43
43
  search_ilike: false,
44
44
 
45
45
  # Other misc attributes.
46
46
  create_from_recordset: true, # Option for `recordset.create` vs `Model.create` behavior.
47
47
  filter_recordset_before_find: true, # Option to control if filtering is done before find.
48
48
  }.each do |a, default|
49
- unless base.respond_to?(a)
50
- base.class_attribute(a)
49
+ next if base.respond_to?(a)
51
50
 
52
- # Set default manually so we can still support Rails 4. Maybe later we can use the default
53
- # parameter on `class_attribute`.
54
- base.send(:"#{a}=", default)
55
- end
51
+ base.class_attribute(a)
52
+
53
+ # Set default manually so we can still support Rails 4. Maybe later we can use the default
54
+ # parameter on `class_attribute`.
55
+ base.send(:"#{a}=", default)
56
56
  end
57
57
  end
58
58
  end
@@ -105,7 +105,7 @@ module RESTFramework::BaseModelControllerMixin
105
105
 
106
106
  # Helper to get the configured serializer class, or `NativeSerializer` as a default.
107
107
  def get_serializer_class
108
- return self.class.serializer_class || RESTFramework::NativeSerializer
108
+ return super || RESTFramework::NativeSerializer
109
109
  end
110
110
 
111
111
  # Helper to get filtering backends, defaulting to using `ModelFilter` and `ModelOrderingFilter`.
@@ -143,8 +143,8 @@ module RESTFramework::BaseModelControllerMixin
143
143
  body_params
144
144
  end
145
145
  end
146
- alias :get_create_params :get_body_params
147
- alias :get_update_params :get_body_params
146
+ alias_method :get_create_params, :get_body_params
147
+ alias_method :get_update_params, :get_body_params
148
148
 
149
149
  # Get the model for this controller.
150
150
  def get_model(from_get_recordset: false)
@@ -160,7 +160,7 @@ module RESTFramework::BaseModelControllerMixin
160
160
 
161
161
  # Try to determine model from controller name.
162
162
  begin
163
- return (@model = self.class.name.demodulize.match(/(.*)Controller/)[1].singularize.constantize)
163
+ return @model = self.class.name.demodulize.match(/(.*)Controller/)[1].singularize.constantize
164
164
  rescue NameError
165
165
  end
166
166
 
@@ -196,48 +196,51 @@ module RESTFramework::BaseModelControllerMixin
196
196
  recordset = self.get_filtered_data(recordset)
197
197
  end
198
198
 
199
- # Return the record.
200
- if find_by_value = params[:id] # Route key is always :id by Rails convention.
201
- return self.get_recordset.find_by!(find_by_key => find_by_value)
202
- end
203
- return nil
199
+ # Return the record. Route key is always :id by Rails convention.
200
+ return recordset.find_by!(find_by_key => params[:id])
204
201
  end
205
202
  end
206
203
 
207
-
208
204
  # Mixin for listing records.
209
205
  module RESTFramework::ListModelMixin
210
206
  def index
207
+ api_response(self._index)
208
+ end
209
+
210
+ def _index
211
211
  @records = self.get_filtered_data(self.get_recordset)
212
212
 
213
213
  # Handle pagination, if enabled.
214
214
  if self.class.paginator_class
215
215
  paginator = self.class.paginator_class.new(data: @records, controller: self)
216
216
  page = paginator.get_page
217
- serialized_page = self.get_serializer_class.new(object: page, controller: self).serialize
218
- data = paginator.get_paginated_response(serialized_page)
217
+ serialized_page = self.get_serializer_class.new(page, controller: self).serialize
218
+ return paginator.get_paginated_response(serialized_page)
219
219
  else
220
- data = self.get_serializer_class.new(object: @records, controller: self).serialize
220
+ return self.get_serializer_class.new(@records, controller: self).serialize
221
221
  end
222
-
223
- return api_response(data)
224
222
  end
225
223
  end
226
224
 
227
-
228
225
  # Mixin for showing records.
229
226
  module RESTFramework::ShowModelMixin
230
227
  def show
228
+ api_response(self._show)
229
+ end
230
+
231
+ def _show
231
232
  @record = self.get_record
232
- serialized_record = self.get_serializer_class.new(object: @record, controller: self).serialize
233
- return api_response(serialized_record)
233
+ return self.get_serializer_class.new(@record, controller: self).serialize
234
234
  end
235
235
  end
236
236
 
237
-
238
237
  # Mixin for creating records.
239
238
  module RESTFramework::CreateModelMixin
240
239
  def create
240
+ api_response(self._create)
241
+ end
242
+
243
+ def _create
241
244
  if self.get_recordset.respond_to?(:create!) && self.create_from_recordset
242
245
  # Create with any properties inherited from the recordset.
243
246
  @record = self.get_recordset.create!(self.get_create_params)
@@ -245,39 +248,43 @@ module RESTFramework::CreateModelMixin
245
248
  # Otherwise, perform a "bare" create.
246
249
  @record = self.get_model.create!(self.get_create_params)
247
250
  end
248
- serialized_record = self.get_serializer_class.new(object: @record, controller: self).serialize
249
- return api_response(serialized_record)
251
+
252
+ return self.get_serializer_class.new(@record, controller: self).serialize
250
253
  end
251
254
  end
252
255
 
253
-
254
256
  # Mixin for updating records.
255
257
  module RESTFramework::UpdateModelMixin
256
258
  def update
259
+ api_response(self._update)
260
+ end
261
+
262
+ def _update
257
263
  @record = self.get_record
258
264
  @record.update!(self.get_update_params)
259
- serialized_record = self.get_serializer_class.new(object: @record, controller: self).serialize
260
- return api_response(serialized_record)
265
+ return self.get_serializer_class.new(@record, controller: self).serialize
261
266
  end
262
267
  end
263
268
 
264
-
265
269
  # Mixin for destroying records.
266
270
  module RESTFramework::DestroyModelMixin
267
271
  def destroy
272
+ self._destroy
273
+ api_response("")
274
+ end
275
+
276
+ def _destroy
268
277
  @record = self.get_record
269
278
  @record.destroy!
270
- api_response('')
271
279
  end
272
280
  end
273
281
 
274
-
275
282
  # Mixin that includes show/list mixins.
276
283
  module RESTFramework::ReadOnlyModelControllerMixin
277
284
  include RESTFramework::BaseModelControllerMixin
278
285
 
279
286
  def self.included(base)
280
- if base.is_a? Class
287
+ if base.is_a?(Class)
281
288
  RESTFramework::BaseModelControllerMixin.included(base)
282
289
  end
283
290
  end
@@ -286,13 +293,12 @@ module RESTFramework::ReadOnlyModelControllerMixin
286
293
  include RESTFramework::ShowModelMixin
287
294
  end
288
295
 
289
-
290
296
  # Mixin that includes all the CRUD mixins.
291
297
  module RESTFramework::ModelControllerMixin
292
298
  include RESTFramework::BaseModelControllerMixin
293
299
 
294
300
  def self.included(base)
295
- if base.is_a? Class
301
+ if base.is_a?(Class)
296
302
  RESTFramework::BaseModelControllerMixin.included(base)
297
303
  end
298
304
  end
@@ -1,6 +1,5 @@
1
1
  module RESTFramework::ControllerMixins
2
2
  end
3
3
 
4
-
5
- require_relative 'controller_mixins/base'
6
- require_relative 'controller_mixins/models'
4
+ require_relative "controller_mixins/base"
5
+ require_relative "controller_mixins/models"
@@ -2,10 +2,9 @@
2
2
  class RESTFramework::Error < StandardError
3
3
  end
4
4
 
5
-
6
5
  class RESTFramework::NilPassedToAPIResponseError < RESTFramework::Error
7
6
  def message
8
- return <<~MSG.split("\n").join(' ')
7
+ return <<~MSG.split("\n").join(" ")
9
8
  Payload of `nil` was passed to `api_response`; this is unsupported. If you want a blank
10
9
  response, pass `''` (an empty string) as the payload. If this was the result of a `find_by`
11
10
  (or similar Active Record method) not finding a record, you should use the bang version (e.g.,
@@ -8,7 +8,6 @@ class RESTFramework::BaseFilter
8
8
  end
9
9
  end
10
10
 
11
-
12
11
  # A simple filtering backend that supports filtering a recordset based on fields defined on the
13
12
  # controller class.
14
13
  class RESTFramework::ModelFilter < RESTFramework::BaseFilter
@@ -31,19 +30,19 @@ class RESTFramework::ModelFilter < RESTFramework::BaseFilter
31
30
  end
32
31
  end
33
32
 
34
-
35
33
  # A filter backend which handles ordering of the recordset.
36
34
  class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
37
35
  # Convert ordering string to an ordering configuration.
38
36
  def _get_ordering
39
37
  return nil if @controller.class.ordering_query_param.blank?
38
+
40
39
  ordering_fields = @controller.send(:get_ordering_fields)
41
40
  order_string = @controller.params[@controller.class.ordering_query_param]
42
41
 
43
42
  unless order_string.blank?
44
43
  ordering = {}
45
- order_string.split(',').each do |field|
46
- if field[0] == '-'
44
+ order_string.split(",").each do |field|
45
+ if field[0] == "-"
47
46
  column = field[1..-1]
48
47
  direction = :desc
49
48
  else
@@ -63,7 +62,7 @@ class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
63
62
  # Order data according to the request query parameters.
64
63
  def get_filtered_data(data)
65
64
  ordering = self._get_ordering
66
- reorder = !@controller.send(:ordering_no_reorder)
65
+ reorder = !@controller.class.ordering_no_reorder
67
66
 
68
67
  if ordering && !ordering.empty?
69
68
  return data.send(reorder ? :reorder : :order, _get_ordering)
@@ -73,19 +72,21 @@ class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
73
72
  end
74
73
  end
75
74
 
76
-
77
75
  # Multi-field text searching on models.
78
76
  class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
79
77
  # Filter data according to the request query parameters.
80
78
  def get_filtered_data(data)
81
79
  fields = @controller.send(:get_search_fields)
82
- search = @controller.request.query_parameters[@controller.send(:search_query_param)]
80
+ search = @controller.request.query_parameters[@controller.class.search_query_param]
83
81
 
84
82
  # Ensure we use array conditions to prevent SQL injection.
85
83
  unless search.blank?
86
- return data.where(fields.map { |f|
87
- "CAST(#{f} AS CHAR) #{@controller.send(:search_ilike) ? "ILIKE" : "LIKE"} ?"
88
- }.join(' OR '), *(["%#{search}%"] * fields.length))
84
+ return data.where(
85
+ fields.map { |f|
86
+ "CAST(#{f} AS CHAR) #{@controller.class.search_ilike ? "ILIKE" : "LIKE"} ?"
87
+ }.join(" OR "),
88
+ *(["%#{search}%"] * fields.length),
89
+ )
89
90
  end
90
91
 
91
92
  return data
@@ -1,7 +1,6 @@
1
- require 'rails/generators'
1
+ require "rails/generators"
2
2
 
3
-
4
- # Some projects don't have the inflection "REST" as an acronym, so this is a helper class to prevent
3
+ # Most projects don't have the inflection "REST" as an acronym, so this is a helper class to prevent
5
4
  # this generator from being namespaced under `r_e_s_t_framework`.
6
5
  # :nocov:
7
6
  class RESTFrameworkCustomGeneratorControllerNamespace < String
@@ -11,18 +10,17 @@ class RESTFrameworkCustomGeneratorControllerNamespace < String
11
10
  end
12
11
  # :nocov:
13
12
 
14
-
15
13
  class RESTFramework::Generators::ControllerGenerator < Rails::Generators::Base
16
- PATH_REGEX = /^\/*([a-z0-9_\/]*[a-z0-9_])(?:[\.a-z\/]*)$/
14
+ PATH_REGEX = %r{^[a-z0-9][a-z0-9_/]+$}
17
15
 
18
16
  desc <<~END
19
- Description:
17
+ Description:
20
18
  Generates a new REST Framework controller.
21
19
 
22
20
  Specify the controller as a path, including the module, if needed, like:
23
21
  'parent_module/controller_name'.
24
22
 
25
- Example:
23
+ Example:
26
24
  `rails generate rest_framework:controller user_api/groups`
27
25
 
28
26
  Generates a controller at `app/controllers/user_api/groups_controller.rb` named
@@ -31,10 +29,7 @@ class RESTFramework::Generators::ControllerGenerator < Rails::Generators::Base
31
29
 
32
30
  argument :path, type: :string
33
31
  class_option(
34
- :parent_class,
35
- type: :string,
36
- default: 'ApplicationController',
37
- desc: "Inheritance parent",
32
+ :parent_class, type: :string, default: "ApplicationController", desc: "Inheritance parent"
38
33
  )
39
34
  class_option(
40
35
  :include_base,
@@ -50,11 +45,13 @@ class RESTFramework::Generators::ControllerGenerator < Rails::Generators::Base
50
45
  end
51
46
 
52
47
  def create_rest_controller_file
53
- unless (path_match = PATH_REGEX.match(self.path))
54
- raise StandardError.new("Path isn't correct.")
48
+ unless PATH_REGEX.match?(self.path)
49
+ raise StandardError, "Path isn't valid."
55
50
  end
56
51
 
57
- cleaned_path = path_match[1]
52
+ # Remove '_controller' from end of path, if it exists.
53
+ cleaned_path = self.path.delete_suffix("_controller")
54
+
58
55
  content = <<~END
59
56
  class #{cleaned_path.camelize}Controller < #{options[:parent_class]}
60
57
  include RESTFramework::#{
@@ -62,6 +59,6 @@ class RESTFramework::Generators::ControllerGenerator < Rails::Generators::Base
62
59
  }
63
60
  end
64
61
  END
65
- create_file("app/controllers/#{path}_controller.rb", content)
62
+ create_file("app/controllers/#{cleaned_path}_controller.rb", content)
66
63
  end
67
64
  end
@@ -1,5 +1,4 @@
1
1
  module RESTFramework::Generators
2
2
  end
3
3
 
4
-
5
- require_relative 'generators/controller_generator'
4
+ require_relative "generators/controller_generator"
@@ -15,7 +15,6 @@ class RESTFramework::BasePaginator
15
15
  end
16
16
  end
17
17
 
18
-
19
18
  # A simple paginator based on page numbers.
20
19
  #
21
20
  # Example: http://example.com/api/users/?page=3&page_size=50
@@ -26,7 +25,7 @@ class RESTFramework::PageNumberPaginator < RESTFramework::BasePaginator
26
25
  @page_size = self._page_size
27
26
 
28
27
  @total_pages = @count / @page_size
29
- @total_pages += 1 if (@count % @page_size != 0)
28
+ @total_pages += 1 if @count % @page_size != 0
30
29
  end
31
30
 
32
31
  def _page_size
@@ -60,7 +59,7 @@ class RESTFramework::PageNumberPaginator < RESTFramework::BasePaginator
60
59
  # Get the page and return it so the caller can serialize it.
61
60
  def get_page(page_number=nil)
62
61
  # If page number isn't provided, infer from the params or use 1 as a fallback value.
63
- if !page_number
62
+ unless page_number
64
63
  page_number = @controller&.params&.[](self._page_query_param)
65
64
  if page_number.blank?
66
65
  page_number = 1
@@ -90,7 +89,6 @@ class RESTFramework::PageNumberPaginator < RESTFramework::BasePaginator
90
89
  end
91
90
  end
92
91
 
93
-
94
92
  # TODO: implement this
95
93
  # class RESTFramework::CountOffsetPaginator
96
94
  # end
@@ -1,11 +1,11 @@
1
- require 'action_dispatch/routing/mapper'
2
- require_relative 'utils'
1
+ require "action_dispatch/routing/mapper"
2
+ require_relative "utils"
3
3
 
4
4
  module ActionDispatch::Routing
5
5
  class Mapper
6
6
  # Internal interface to get the controller class from the name and current scope.
7
7
  protected def _get_controller_class(name, pluralize: true, fallback_reverse_pluralization: true)
8
- # get class name
8
+ # Get class name.
9
9
  name = name.to_s.camelize # camelize to leave plural names plural
10
10
  name = name.pluralize if pluralize
11
11
  if name == name.pluralize
@@ -16,14 +16,14 @@ module ActionDispatch::Routing
16
16
  name += "Controller"
17
17
  name_reverse += "Controller"
18
18
 
19
- # get scope for the class
19
+ # Get scope for the class.
20
20
  if @scope[:module]
21
- mod = @scope[:module].to_s.classify.constantize
21
+ mod = @scope[:module].to_s.camelize.constantize
22
22
  else
23
23
  mod = Object
24
24
  end
25
25
 
26
- # convert class name to class
26
+ # Convert class name to class.
27
27
  begin
28
28
  controller = mod.const_get(name)
29
29
  rescue NameError
@@ -83,15 +83,15 @@ module ActionDispatch::Routing
83
83
  public_send(resource_method, name, except: skip, **kwargs) do
84
84
  if controller_class.respond_to?(:extra_member_actions)
85
85
  member do
86
- actions = RESTFramework::Utils::parse_extra_actions(
87
- controller_class.extra_member_actions
86
+ actions = RESTFramework::Utils.parse_extra_actions(
87
+ controller_class.extra_member_actions,
88
88
  )
89
89
  self._route_extra_actions(actions)
90
90
  end
91
91
  end
92
92
 
93
93
  collection do
94
- actions = RESTFramework::Utils::parse_extra_actions(controller_class.extra_actions)
94
+ actions = RESTFramework::Utils.parse_extra_actions(controller_class.extra_actions)
95
95
  self._route_extra_actions(actions)
96
96
  end
97
97
 
@@ -127,11 +127,11 @@ module ActionDispatch::Routing
127
127
  kwargs[:controller] = name unless kwargs[:controller]
128
128
 
129
129
  # Route actions using the resourceful router, but skip all builtin actions.
130
- actions = RESTFramework::Utils::parse_extra_actions(controller_class.extra_actions)
130
+ actions = RESTFramework::Utils.parse_extra_actions(controller_class.extra_actions)
131
131
  public_send(:resource, name, only: [], **kwargs) do
132
132
  # Route a root for this resource.
133
133
  if route_root_to
134
- get '', action: route_root_to
134
+ get("", action: route_root_to)
135
135
  end
136
136
 
137
137
  self._route_extra_actions(actions, &block)
@@ -146,13 +146,12 @@ module ActionDispatch::Routing
146
146
 
147
147
  # Remove path if name is nil (routing to the root of current namespace).
148
148
  unless name
149
- kwargs[:path] = ''
149
+ kwargs[:path] = ""
150
150
  end
151
151
 
152
152
  return rest_route(controller, route_root_to: root_action, **kwargs) do
153
153
  yield if block_given?
154
154
  end
155
155
  end
156
-
157
156
  end
158
157
  end
@@ -1,14 +1,23 @@
1
+ # The base serializer defines the interface for all REST Framework serializers.
1
2
  class RESTFramework::BaseSerializer
2
- def initialize(object: nil, controller: nil, **kwargs)
3
+ attr_accessor :object
4
+
5
+ def initialize(object=nil, controller: nil, **kwargs)
3
6
  @object = object
4
7
  @controller = controller
5
8
  end
6
9
 
7
- def serialize
10
+ # The primary interface for extracting a native Ruby types. This works both for records and
11
+ # collections.
12
+ def serialize(**kwargs)
8
13
  raise NotImplementedError
9
14
  end
10
- end
11
15
 
16
+ # Synonym for `serializable_hash` or compatibility with ActiveModelSerializers.
17
+ def serializable_hash(**kwargs)
18
+ return self.serialize(**kwargs)
19
+ end
20
+ end
12
21
 
13
22
  # This serializer uses `.serializable_hash` to convert objects to Ruby primitives (with the
14
23
  # top-level being either an array or a hash).
@@ -18,8 +27,8 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
18
27
  class_attribute :plural_config
19
28
  class_attribute :action_config
20
29
 
21
- def initialize(many: nil, model: nil, **kwargs)
22
- super(**kwargs)
30
+ def initialize(object=nil, many: nil, model: nil, **kwargs)
31
+ super(object, **kwargs)
23
32
 
24
33
  if many.nil?
25
34
  # Determine if we are dealing with many objects or just one.
@@ -31,9 +40,9 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
31
40
  # Determine model either explicitly, or by inspecting @object or @controller.
32
41
  @model = model
33
42
  @model ||= @object.class if @object.is_a?(ActiveRecord::Base)
34
- @model ||= @object[0].class if (
43
+ @model ||= @object[0].class if
35
44
  @many && @object.is_a?(Enumerable) && @object.is_a?(ActiveRecord::Base)
36
- )
45
+
37
46
  @model ||= @controller.send(:get_model) if @controller
38
47
  end
39
48
 
@@ -66,23 +75,77 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
66
75
  return nil unless @controller
67
76
 
68
77
  if @many == true
69
- controller_serializer = @controller.try(:native_serializer_plural_config)
78
+ controller_serializer = @controller.class.try(:native_serializer_plural_config)
70
79
  elsif @many == false
71
- controller_serializer = @controller.try(:native_serializer_singular_config)
80
+ controller_serializer = @controller.class.try(:native_serializer_singular_config)
72
81
  end
73
82
 
74
- return controller_serializer || @controller.try(:native_serializer_config)
83
+ return controller_serializer || @controller.class.try(:native_serializer_config)
75
84
  end
76
85
 
77
- # Get a configuration passable to `serializable_hash` for the object.
78
- def get_serializer_config
86
+ # Helper to filter (mutate) a single subconfig for specific keys.
87
+ def self.filter_subconfig(subconfig, except, additive: false)
88
+ return subconfig if !subconfig && !additive
89
+
90
+ if subconfig.is_a?(Array)
91
+ subconfig = subconfig.map(&:to_sym)
92
+ if additive
93
+ # Only add fields which are not already included.
94
+ subconfig += except - subconfig
95
+ else
96
+ subconfig -= except
97
+ end
98
+ elsif subconfig.is_a?(Hash)
99
+ # Additive doesn't make sense in a hash context since we wouldn't know the values.
100
+ unless additive
101
+ subconfig.symbolize_keys!
102
+ subconfig.reject! { |k, _v| k.in?(except) }
103
+ end
104
+ elsif !subconfig
105
+ else # Subconfig is a single element (assume string/symbol).
106
+ subconfig = subconfig.to_sym
107
+ if subconfig.in?(except)
108
+ subconfig = [] unless additive
109
+ elsif additive
110
+ subconfig = [subconfig, *except]
111
+ end
112
+ end
113
+
114
+ return subconfig
115
+ end
116
+
117
+ # Helper to filter out configuration properties based on the :except query parameter.
118
+ def filter_except(config)
119
+ return config unless @controller
120
+
121
+ except_query_param = @controller.class.try(:native_serializer_except_query_param)
122
+ if except = @controller.request.query_parameters[except_query_param].presence
123
+ except = except.split(",").map(&:strip).map(&:to_sym)
124
+
125
+ unless except.empty?
126
+ # Duplicate the config to avoid mutating class state.
127
+ config = config.deep_dup
128
+
129
+ # Filter `only`, `except` (additive), `include`, and `methods`.
130
+ config[:only] = self.class.filter_subconfig(config[:only], except)
131
+ config[:except] = self.class.filter_subconfig(config[:except], except, additive: true)
132
+ config[:include] = self.class.filter_subconfig(config[:include], except)
133
+ config[:methods] = self.class.filter_subconfig(config[:methods], except)
134
+ end
135
+ end
136
+
137
+ return config
138
+ end
139
+
140
+ # Get the raw serializer config.
141
+ def _get_raw_serializer_config
79
142
  # Return a locally defined serializer config if one is defined.
80
143
  if local_config = self.get_local_native_serializer_config
81
144
  return local_config
82
145
  end
83
146
 
84
147
  # Return a serializer config if one is defined on the controller.
85
- if serializer_config = get_controller_native_serializer_config
148
+ if serializer_config = self.get_controller_native_serializer_config
86
149
  return serializer_config
87
150
  end
88
151
 
@@ -103,13 +166,16 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
103
166
  return {}
104
167
  end
105
168
 
106
- # Convert the object (record or recordset) to Ruby primitives.
107
- def serialize
108
- raise "No object available to serialize!" unless @object
169
+ # Get a configuration passable to `serializable_hash` for the object, filtered if required.
170
+ def get_serializer_config
171
+ return filter_except(self._get_raw_serializer_config)
172
+ end
109
173
 
110
- if @object.is_a?(Enumerable)
174
+ def serialize(**kwargs)
175
+ if @object.respond_to?(:to_ary)
111
176
  return @object.map { |r| r.serializable_hash(self.get_serializer_config) }
112
177
  end
178
+
113
179
  return @object.serializable_hash(self.get_serializer_config)
114
180
  end
115
181
 
@@ -118,6 +184,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
118
184
  @_nested_config ||= self.get_serializer_config
119
185
  return @_nested_config[key]
120
186
  end
187
+
121
188
  def []=(key, value)
122
189
  @_nested_config ||= self.get_serializer_config
123
190
  return @_nested_config[key] = value
@@ -128,20 +195,20 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
128
195
  @_nested_config ||= self.new.get_serializer_config
129
196
  return @_nested_config[key]
130
197
  end
198
+
131
199
  def self.[]=(key, value)
132
200
  @_nested_config ||= self.new.get_serializer_config
133
201
  return @_nested_config[key] = value
134
202
  end
135
203
  end
136
204
 
137
-
138
205
  # :nocov:
139
206
  # Alias NativeModelSerializer -> NativeSerializer.
140
207
  class RESTFramework::NativeModelSerializer < RESTFramework::NativeSerializer
141
208
  def initialize(**kwargs)
142
209
  super
143
210
  ActiveSupport::Deprecation.warn(
144
- <<~MSG.split("\n").join(' ')
211
+ <<~MSG.split("\n").join(" "),
145
212
  RESTFramework::NativeModelSerializer is deprecated and will be removed in future versions of
146
213
  REST Framework; you should use RESTFramework::NativeSerializer instead.
147
214
  MSG
@@ -149,3 +216,19 @@ class RESTFramework::NativeModelSerializer < RESTFramework::NativeSerializer
149
216
  end
150
217
  end
151
218
  # :nocov:
219
+
220
+ # This is a helper factory to wrap an ActiveModelSerializer to provide a `serialize` method which
221
+ # accepts both collections and individual records. Use `.for` to build adapters.
222
+ class RESTFramework::ActiveModelSerializerAdapterFactory
223
+ def self.for(active_model_serializer)
224
+ return Class.new(active_model_serializer) do
225
+ def serialize
226
+ if self.object.respond_to?(:to_ary)
227
+ return self.object.map { |r| self.class.superclass.new(r).serializable_hash }
228
+ end
229
+
230
+ return self.serializable_hash
231
+ end
232
+ end
233
+ end
234
+ end
@@ -2,7 +2,7 @@ module RESTFramework::Utils
2
2
  # Helper to take extra_actions hash and convert to a consistent format:
3
3
  # `{paths:, methods:, kwargs:}`.
4
4
  def self.parse_extra_actions(extra_actions)
5
- return (extra_actions || {}).map do |k,v|
5
+ return (extra_actions || {}).map do |k, v|
6
6
  kwargs = {action: k}
7
7
  path = k
8
8
 
@@ -38,7 +38,7 @@ module RESTFramework::Utils
38
38
  # Helper to get the current route pattern, stripped of the `(:format)` segment.
39
39
  def self.get_route_pattern(application_routes, request)
40
40
  application_routes.router.recognize(request) do |route, _, _|
41
- return route.path.spec.to_s.gsub(/\(\.:format\)$/, '')
41
+ return route.path.spec.to_s.gsub(/\(\.:format\)$/, "")
42
42
  end
43
43
  end
44
44
 
@@ -18,7 +18,7 @@ module RESTFramework
18
18
  end
19
19
 
20
20
  # No VERSION file, so version is unknown.
21
- return 'unknown'
21
+ return "unknown"
22
22
  end
23
23
 
24
24
  def self.stamp_version
@@ -30,5 +30,5 @@ module RESTFramework
30
30
  end
31
31
  end
32
32
 
33
- VERSION = Version.get_version()
33
+ VERSION = Version.get_version
34
34
  end
@@ -1,7 +1,6 @@
1
1
  module RESTFramework
2
2
  end
3
3
 
4
-
5
4
  require_relative "rest_framework/controller_mixins"
6
5
  require_relative "rest_framework/engine"
7
6
  require_relative "rest_framework/errors"
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.4.1
4
+ version: 0.5.2
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: 2021-09-10 00:00:00.000000000 Z
11
+ date: 2022-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -74,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  requirements: []
77
- rubygems_version: 3.1.4
77
+ rubygems_version: 3.2.22
78
78
  signing_key:
79
79
  specification_version: 4
80
80
  summary: A framework for DRY RESTful APIs in Ruby on Rails.