rest_framework 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e654cef83c3cf4505697e0417d637489a248b691ca157100b6a12dc3e0f3f9e
4
- data.tar.gz: b656446d2fd724e2b91708e4df6248cd5e6676a311e8cd23d76f07951758bffd
3
+ metadata.gz: 9791b0cbb0a806afa00955abf151c7d04bd1820bdbe189f237badc9782f87d6b
4
+ data.tar.gz: 69e29341f8804fac7894c0065b0f5535d88b0e8d60577bc77677fe13d633f2d5
5
5
  SHA512:
6
- metadata.gz: 3a9ee1d172c5e33813352b2190f2f38540226d93d84bf77cca34fab326231f6215a702c66e3965f5239d652e35eeb36a7286210826608e11ab9a35c8edf65704
7
- data.tar.gz: e132e15651e2c9fd0cd9d1338ec2c5a795d369a6a001c2e4a957c7912dfd5539ea940945ea48c011e1c4be6e3b1feaf55b63934fdff5608135c2ac089fd83486
6
+ metadata.gz: f04bf8059e765db9e13a9729db43c2b26a914035063fe728e9ad0bcb1ab968bb8939a4d278e239a39f64b671d183bfdd7a2487248495cd7e5bd26b93ac49a103
7
+ data.tar.gz: '0908d43c8b2188e4dde5920a84eb621c0dea2dc170891e314d77d95b823a32af6681ae9a6028277f32282b88be03f8e95da55ec310f75e3c89ec8c3a09934f09'
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.0
@@ -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
21
  mod = @scope[:module].to_s.classify.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,66 @@ 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 unless subconfig
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
+ subconfig.symbolize_keys!
100
+ subconfig.reject! { |k, _v| k.in?(except) }
101
+ end
102
+
103
+ return subconfig
104
+ end
105
+
106
+ # Helper to filter out configuration properties based on the :except query parameter.
107
+ def filter_except(config)
108
+ return config unless @controller
109
+
110
+ except_query_param = @controller.class.try(:native_serializer_except_query_param)
111
+ if except = @controller.request.query_parameters[except_query_param]
112
+ except = except.split(",").map(&:strip).map(&:to_sym)
113
+
114
+ unless except.empty?
115
+ # Duplicate the config to avoid mutating class state.
116
+ config = config.deep_dup
117
+
118
+ # Filter `only`, `except` (additive), `include`, and `methods`.
119
+ self.class.filter_subconfig(config[:only], except)
120
+ self.class.filter_subconfig(config[:except], except, additive: true)
121
+ self.class.filter_subconfig(config[:include], except)
122
+ self.class.filter_subconfig(config[:methods], except)
123
+ end
124
+ end
125
+
126
+ return config
127
+ end
128
+
129
+ # Get the raw serializer config.
130
+ def _get_raw_serializer_config
79
131
  # Return a locally defined serializer config if one is defined.
80
132
  if local_config = self.get_local_native_serializer_config
81
133
  return local_config
82
134
  end
83
135
 
84
136
  # Return a serializer config if one is defined on the controller.
85
- if serializer_config = get_controller_native_serializer_config
137
+ if serializer_config = self.get_controller_native_serializer_config
86
138
  return serializer_config
87
139
  end
88
140
 
@@ -103,13 +155,16 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
103
155
  return {}
104
156
  end
105
157
 
106
- # Convert the object (record or recordset) to Ruby primitives.
107
- def serialize
108
- raise "No object available to serialize!" unless @object
158
+ # Get a configuration passable to `serializable_hash` for the object, filtered if required.
159
+ def get_serializer_config
160
+ return filter_except(self._get_raw_serializer_config)
161
+ end
109
162
 
110
- if @object.is_a?(Enumerable)
163
+ def serialize(**kwargs)
164
+ if @object.respond_to?(:to_ary)
111
165
  return @object.map { |r| r.serializable_hash(self.get_serializer_config) }
112
166
  end
167
+
113
168
  return @object.serializable_hash(self.get_serializer_config)
114
169
  end
115
170
 
@@ -118,6 +173,7 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
118
173
  @_nested_config ||= self.get_serializer_config
119
174
  return @_nested_config[key]
120
175
  end
176
+
121
177
  def []=(key, value)
122
178
  @_nested_config ||= self.get_serializer_config
123
179
  return @_nested_config[key] = value
@@ -128,20 +184,20 @@ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
128
184
  @_nested_config ||= self.new.get_serializer_config
129
185
  return @_nested_config[key]
130
186
  end
187
+
131
188
  def self.[]=(key, value)
132
189
  @_nested_config ||= self.new.get_serializer_config
133
190
  return @_nested_config[key] = value
134
191
  end
135
192
  end
136
193
 
137
-
138
194
  # :nocov:
139
195
  # Alias NativeModelSerializer -> NativeSerializer.
140
196
  class RESTFramework::NativeModelSerializer < RESTFramework::NativeSerializer
141
197
  def initialize(**kwargs)
142
198
  super
143
199
  ActiveSupport::Deprecation.warn(
144
- <<~MSG.split("\n").join(' ')
200
+ <<~MSG.split("\n").join(" "),
145
201
  RESTFramework::NativeModelSerializer is deprecated and will be removed in future versions of
146
202
  REST Framework; you should use RESTFramework::NativeSerializer instead.
147
203
  MSG
@@ -149,3 +205,19 @@ class RESTFramework::NativeModelSerializer < RESTFramework::NativeSerializer
149
205
  end
150
206
  end
151
207
  # :nocov:
208
+
209
+ # This is a helper factory to wrap an ActiveModelSerializer to provide a `serialize` method which
210
+ # accepts both collections and individual records. Use `.for` to build adapters.
211
+ class RESTFramework::ActiveModelSerializerAdapterFactory
212
+ def self.for(active_model_serializer)
213
+ return Class.new(active_model_serializer) do
214
+ def serialize
215
+ if self.object.respond_to?(:to_ary)
216
+ return self.object.map { |r| self.class.superclass.new(r).serializable_hash }
217
+ end
218
+
219
+ return self.serializable_hash
220
+ end
221
+ end
222
+ end
223
+ 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.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: 2021-09-10 00:00:00.000000000 Z
11
+ date: 2022-03-05 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.