rest_framework 0.0.15 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ # Top-level class for all REST Framework errors.
2
+ class RESTFramework::Error < StandardError
3
+ end
4
+
5
+ class RESTFramework::NilPassedToAPIResponseError < RESTFramework::Error
6
+ def message
7
+ return <<~MSG.split("\n").join(' ')
8
+ Payload of `nil` was passed to `api_response`; this is unsupported. If you want a blank
9
+ response, pass `''` (an empty string) as the payload. If this was the result of a `find_by`
10
+ (or similar Active Record method) not finding a record, you should use the bang version (e.g.,
11
+ `find_by!`) to raise `ActiveRecord::RecordNotFound`, which the REST controller will catch and
12
+ return an appropriate error response.
13
+ MSG
14
+ end
15
+ end
16
+
17
+ class RESTFramework::UnserializableError < RESTFramework::Error
18
+ def initialize(object)
19
+ @object = object
20
+ return super
21
+ end
22
+
23
+ def message
24
+ return "Unable to serialize `#{@object.inspect}` (of type `#{@object.class}`)."
25
+ end
26
+ end
@@ -0,0 +1,68 @@
1
+ class RESTFramework::BaseFilter
2
+ def initialize(controller:)
3
+ @controller = controller
4
+ end
5
+
6
+ def get_filtered_data(data)
7
+ raise NotImplementedError
8
+ end
9
+ end
10
+
11
+
12
+ # A simple filtering backend that supports filtering a recordset based on fields defined on the
13
+ # controller class.
14
+ class RESTFramework::ModelFilter < RESTFramework::BaseFilter
15
+ # Filter params for keys allowed by the current action's filterset_fields/fields config.
16
+ def _get_filter_params
17
+ fields = @controller.class.filterset_fields&.map(&:to_s) || @controller.send(:get_fields)
18
+ return @controller.request.query_parameters.select { |p|
19
+ fields.include?(p)
20
+ }.to_h.symbolize_keys # convert from HashWithIndifferentAccess to Hash w/keys
21
+ end
22
+
23
+ # Filter data according to the request query parameters.
24
+ def get_filtered_data(data)
25
+ filter_params = self._get_filter_params
26
+ unless filter_params.blank?
27
+ return data.where(**filter_params)
28
+ end
29
+
30
+ return data
31
+ end
32
+ end
33
+
34
+
35
+ # A filter backend which handles ordering of the recordset.
36
+ class RESTFramework::ModelOrderingFilter < RESTFramework::BaseFilter
37
+ # Convert ordering string to an ordering configuration.
38
+ def _get_ordering
39
+ return nil unless @controller.class.ordering_query_param
40
+
41
+ order_string = @controller.params[@controller.class.ordering_query_param]
42
+ unless order_string.blank?
43
+ return order_string.split(',').map { |field|
44
+ if field[0] == '-'
45
+ [field[1..-1].to_sym, :desc]
46
+ else
47
+ [field.to_sym, :asc]
48
+ end
49
+ }.to_h
50
+ end
51
+
52
+ return nil
53
+ end
54
+
55
+ # Order data according to the request query parameters.
56
+ def get_filtered_data(data)
57
+ ordering = self._get_ordering
58
+ if ordering && !ordering.empty?
59
+ return data.order(_get_ordering)
60
+ end
61
+ return data
62
+ end
63
+ end
64
+
65
+
66
+ # TODO: implement searching within fields rather than exact match filtering (ModelFilter)
67
+ # class RESTFramework::ModelSearchFilter < RESTFramework::BaseFilter
68
+ # end
@@ -0,0 +1,5 @@
1
+ module RESTFramework::Generators
2
+ end
3
+
4
+
5
+ require_relative 'generators/controller_generator'
@@ -0,0 +1,26 @@
1
+ require 'rails/generators'
2
+
3
+
4
+ class RESTFramework::Generators::ControllerGenerator < Rails::Generators::Base
5
+ desc <<~END
6
+ Description:
7
+ Stubs out a active_scaffolded controller. Pass the model name,
8
+ either CamelCased or under_scored.
9
+ The controller name is retrieved as a pluralized version of the model
10
+ name.
11
+ To create a controller within a module, specify the model name as a
12
+ path like 'parent_module/controller_name'.
13
+ This generates a controller class in app/controllers and invokes helper,
14
+ template engine and test framework generators.
15
+ Example:
16
+ `rails generate rest_framework:controller CreditCard`
17
+ Credit card controller with URLs like /credit_card/debit.
18
+ Controller: app/controllers/credit_cards_controller.rb
19
+ Functional Test: test/functional/credit_cards_controller_test.rb
20
+ Helper: app/helpers/credit_cards_helper.rb
21
+ END
22
+
23
+ def create_controller_file
24
+ create_file "app/controllers/some_controller.rb", "# Add initialization content here"
25
+ end
26
+ end
@@ -0,0 +1,95 @@
1
+ class RESTFramework::BasePaginator
2
+ def initialize(data:, controller:, **kwargs)
3
+ @data = data
4
+ @controller = controller
5
+ end
6
+
7
+ # Get the page and return it so the caller can serialize it.
8
+ def get_page
9
+ raise NotImplementedError
10
+ end
11
+
12
+ # Wrap the serialized page with appropriate metadata.
13
+ def get_paginated_response(serialized_page)
14
+ raise NotImplementedError
15
+ end
16
+ end
17
+
18
+
19
+ # A simple paginator based on page numbers.
20
+ #
21
+ # Example: http://example.com/api/users/?page=3&page_size=50
22
+ class RESTFramework::PageNumberPaginator < RESTFramework::BasePaginator
23
+ def initialize(**kwargs)
24
+ super
25
+ @count = @data.count
26
+ @page_size = self._page_size
27
+
28
+ @total_pages = @count / @page_size
29
+ @total_pages += 1 if (@count % @page_size != 0)
30
+ end
31
+
32
+ def _page_size
33
+ page_size = nil
34
+
35
+ # Get from context, if allowed.
36
+ if @controller.class.page_size_query_param
37
+ if page_size = @controller.params[@controller.class.page_size_query_param].presence
38
+ page_size = page_size.to_i
39
+ end
40
+ end
41
+
42
+ # Otherwise, get from config.
43
+ if !page_size && @controller.class.page_size
44
+ page_size = @controller.class.page_size
45
+ end
46
+
47
+ # Ensure we don't exceed the max page size.
48
+ if @controller.class.max_page_size && page_size > @controller.class.max_page_size
49
+ page_size = @controller.class.max_page_size
50
+ end
51
+
52
+ # Ensure we return at least 1.
53
+ return page_size.zero? ? 1 : page_size
54
+ end
55
+
56
+ def _page_query_param
57
+ return @controller.class.page_query_param&.to_sym
58
+ end
59
+
60
+ # Get the page and return it so the caller can serialize it.
61
+ def get_page(page_number=nil)
62
+ # If page number isn't provided, infer from the params or use 1 as a fallback value.
63
+ if !page_number
64
+ page_number = @controller&.params&.[](self._page_query_param)
65
+ if page_number.blank?
66
+ page_number = 1
67
+ else
68
+ page_number = page_number.to_i
69
+ if page_number.zero?
70
+ page_number = 1
71
+ end
72
+ end
73
+ end
74
+ @page_number = page_number
75
+
76
+ # Get the data page and return it so the caller can serialize the data in the proper format.
77
+ page_index = @page_number - 1
78
+ return @data.limit(@page_size).offset(page_index * @page_size)
79
+ end
80
+
81
+ # Wrap the serialized page with appropriate metadata. TODO: include links.
82
+ def get_paginated_response(serialized_page)
83
+ return {
84
+ count: @count,
85
+ page: @page_number,
86
+ total_pages: @total_pages,
87
+ results: serialized_page,
88
+ }
89
+ end
90
+ end
91
+
92
+
93
+ # TODO: implement this
94
+ # class RESTFramework::CountOffsetPaginator
95
+ # end
@@ -1,8 +1,44 @@
1
1
  require 'action_dispatch/routing/mapper'
2
2
 
3
+
3
4
  module ActionDispatch::Routing
4
5
  class Mapper
5
- # Private interface to get the controller class from the name and current scope.
6
+ # Internal helper to take extra_actions hash and convert to a consistent format.
7
+ protected def _parse_extra_actions(extra_actions)
8
+ return (extra_actions || {}).map do |k,v|
9
+ kwargs = {}
10
+ path = k
11
+
12
+ # Convert structure to path/methods/kwargs.
13
+ if v.is_a?(Hash) # allow kwargs
14
+ v = v.symbolize_keys
15
+
16
+ # Ensure methods is an array.
17
+ if v[:methods].is_a?(String) || v[:methods].is_a?(Symbol)
18
+ methods = [v.delete(:methods)]
19
+ else
20
+ methods = v.delete(:methods)
21
+ end
22
+
23
+ # Override path if it's provided.
24
+ if v.key?(:path)
25
+ path = v.delete(:path)
26
+ end
27
+
28
+ # Pass any further kwargs to the underlying Rails interface.
29
+ kwargs = kwargs.merge(v)
30
+ elsif v.is_a?(Symbol) || v.is_a?(String)
31
+ methods = [v]
32
+ else
33
+ methods = v
34
+ end
35
+
36
+ # Return a hash with keys: :path, :methods, :kwargs.
37
+ {path: path, methods: methods, kwargs: kwargs}
38
+ end
39
+ end
40
+
41
+ # Internal interface to get the controller class from the name and current scope.
6
42
  protected def _get_controller_class(name, pluralize: true, fallback_reverse_pluralization: true)
7
43
  # get class name
8
44
  name = name.to_s.camelize # camelize to leave plural names plural
@@ -36,20 +72,20 @@ module ActionDispatch::Routing
36
72
  return controller
37
73
  end
38
74
 
39
- # Core implementation of the `rest_resource(s)` router, both singular and plural.
75
+ # Internal core implementation of the `rest_resource(s)` router, both singular and plural.
40
76
  # @param default_singular [Boolean] the default plurality of the resource if the plurality is
41
77
  # not otherwise defined by the controller
42
78
  # @param name [Symbol] the resource name, from which path and controller are deduced by default
43
- # @param skip_undefined [Boolean] whether we should skip routing undefined actions
79
+ # @param skip_undefined [Boolean] whether we should skip routing undefined resourceful actions
44
80
  protected def _rest_resources(default_singular, name, skip_undefined: true, **kwargs, &block)
45
- controller = kwargs[:controller] || name
81
+ controller = kwargs.delete(:controller) || name
46
82
  if controller.is_a?(Class)
47
83
  controller_class = controller
48
84
  else
49
85
  controller_class = _get_controller_class(controller, pluralize: !default_singular)
50
86
  end
51
87
 
52
- # set controller if it's not explicitly set
88
+ # Set controller if it's not explicitly set.
53
89
  kwargs[:controller] = name unless kwargs[:controller]
54
90
 
55
91
  # determine plural/singular resource
@@ -70,25 +106,20 @@ module ActionDispatch::Routing
70
106
  public_send(resource_method, name, except: skip, **kwargs) do
71
107
  if controller_class.respond_to?(:extra_member_actions)
72
108
  member do
73
- actions = controller_class.extra_member_actions || {}
74
- actions = actions.select { |k,v| controller_class.method_defined?(k) } if skip_undefined
75
- actions.each do |action, methods|
76
- methods = [methods] if methods.is_a?(Symbol) || methods.is_a?(String)
77
- methods.each do |m|
78
- public_send(m, action)
109
+ actions = self._parse_extra_actions(controller_class.extra_member_actions)
110
+ actions.each do |action_config|
111
+ action_config[:methods].each do |m|
112
+ public_send(m, action_config[:path], **action_config[:kwargs])
79
113
  end
80
114
  end
81
115
  end
82
116
  end
83
117
 
84
118
  collection do
85
- actions = controller_class.extra_actions || {}
86
- actions = actions.select { |k,v| controller_class.method_defined?(k) } if skip_undefined
87
- actions.reject! { |k,v| skip.include? k }
88
- actions.each do |action, methods|
89
- methods = [methods] if methods.is_a?(Symbol) || methods.is_a?(String)
90
- methods.each do |m|
91
- public_send(m, action)
119
+ actions = self._parse_extra_actions(controller_class.extra_actions)
120
+ actions.each do |action_config|
121
+ action_config[:methods].each do |m|
122
+ public_send(m, action_config[:path], **action_config[:kwargs])
92
123
  end
93
124
  end
94
125
  end
@@ -112,42 +143,55 @@ module ActionDispatch::Routing
112
143
  end
113
144
 
114
145
  # Route a controller without the default resourceful paths.
115
- def rest_route(path=nil, skip_undefined: true, **kwargs, &block)
116
- controller = kwargs.delete(:controller) || path
117
- path = path.to_s
146
+ def rest_route(name=nil, **kwargs, &block)
147
+ controller = kwargs.delete(:controller) || name
148
+ if controller.is_a?(Class)
149
+ controller_class = controller
150
+ else
151
+ controller_class = self._get_controller_class(controller, pluralize: false)
152
+ end
118
153
 
119
- # route actions
120
- controller_class = self._get_controller_class(controller, pluralize: false)
121
- skip = controller_class.get_skip_actions(skip_undefined: skip_undefined)
122
- actions = controller_class.extra_actions || {}
123
- actions = actions.select { |k,v| controller_class.method_defined?(k) } if skip_undefined
124
- actions.reject! { |k,v| skip.include? k }
125
- actions.each do |action, methods|
126
- methods = [methods] if methods.is_a?(Symbol) || methods.is_a?(String)
127
- methods.each do |m|
128
- public_send(m, File.join(path, action.to_s), controller: controller, action: action)
154
+ # Set controller if it's not explicitly set.
155
+ kwargs[:controller] = name unless kwargs[:controller]
156
+
157
+ # Route actions using the resourceful router, but skip all builtin actions.
158
+ actions = self._parse_extra_actions(controller_class.extra_actions)
159
+ public_send(:resource, name, only: [], **kwargs) do
160
+ actions.each do |action_config|
161
+ action_config[:methods].each do |m|
162
+ public_send(m, action_config[:path], **action_config[:kwargs])
163
+ end
164
+ yield if block_given?
129
165
  end
130
- yield if block_given?
131
166
  end
132
167
  end
133
168
 
134
169
  # Route a controller's `#root` to '/' in the current scope/namespace, along with other actions.
135
- # @param label [Symbol] the snake_case name of the controller
136
- def rest_root(path=nil, **kwargs, &block)
137
- # by default, use RootController#root
170
+ # @param name [Symbol] the snake_case name of the controller
171
+ def rest_root(name=nil, **kwargs, &block)
172
+ # By default, use RootController#root.
138
173
  root_action = kwargs.delete(:action) || :root
139
- controller = kwargs.delete(:controller) || path || :root
140
- path = path.to_s
174
+ controller = kwargs.delete(:controller) || name || :root
141
175
 
142
- # route the root
143
- get path, controller: controller, action: root_action
176
+ # Route the root.
177
+ get name.to_s, controller: controller, action: root_action
144
178
 
145
- # route any additional actions
179
+ # Route any additional actions.
146
180
  controller_class = self._get_controller_class(controller, pluralize: false)
147
- (controller_class.extra_actions || {}).each do |action, methods|
148
- methods = [methods] if methods.is_a?(Symbol) || methods.is_a?(String)
149
- methods.each do |m|
150
- public_send(m, File.join(path, action.to_s), controller: controller, action: action)
181
+ actions = self._parse_extra_actions(controller_class.extra_actions)
182
+ actions.each do |action_config|
183
+ # Add :action unless kwargs defines it.
184
+ unless action_config[:kwargs].key?(:action)
185
+ action_config[:kwargs][:action] = action_config[:path]
186
+ end
187
+
188
+ action_config[:methods].each do |m|
189
+ public_send(
190
+ m,
191
+ File.join(name.to_s, action_config[:path].to_s),
192
+ controller: controller,
193
+ **action_config[:kwargs],
194
+ )
151
195
  end
152
196
  yield if block_given?
153
197
  end
@@ -1,108 +1,159 @@
1
- module RESTFramework
2
- class BaseSerializer
3
- attr_reader :errors
4
-
5
- def initialize(object: nil, data: nil, controller: nil, **kwargs)
6
- @object = object
7
- @data = data
8
- @controller = controller
9
- end
1
+ class RESTFramework::BaseSerializer
2
+ def initialize(object: nil, controller: nil, **kwargs)
3
+ @object = object
4
+ @controller = controller
10
5
  end
6
+ end
7
+
8
+
9
+ # This serializer uses `.serializable_hash` to convert objects to Ruby primitives (with the
10
+ # top-level being either an array or a hash).
11
+ class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
12
+ class_attribute :config
13
+ class_attribute :singular_config
14
+ class_attribute :plural_config
15
+ class_attribute :action_config
16
+
17
+ def initialize(many: nil, model: nil, **kwargs)
18
+ super(**kwargs)
11
19
 
12
- # This serializer uses `.as_json` to serialize objects. Despite the name, `.as_json` is a Rails
13
- # method which converts objects to Ruby primitives (with the top-level being either an array or a
14
- # hash).
15
- class NativeModelSerializer < BaseSerializer
16
- class_attribute :config
17
- class_attribute :singular_config
18
- class_attribute :plural_config
19
- class_attribute :action_config
20
-
21
- def initialize(model: nil, many: nil, **kwargs)
22
- super(**kwargs)
20
+ if many.nil?
21
+ # Determine if we are dealing with many objects or just one.
22
+ @many = @object.is_a?(Enumerable)
23
+ else
23
24
  @many = many
24
- @model = model || (@controller ? @controller.send(:get_model) : nil)
25
25
  end
26
26
 
27
- # Get controller action, if possible.
28
- def get_action
29
- return @controller&.action_name&.to_sym
27
+ # Determine model either explicitly, or by inspecting @object or @controller.
28
+ @model = model
29
+ @model ||= @object.class if @object.is_a?(ActiveRecord::Base)
30
+ @model ||= @object[0].class if @many && @object[0].is_a?(ActiveRecord::Base)
31
+ @model ||= @controller.send(:get_model) if @controller
32
+ end
33
+
34
+ # Get controller action, if possible.
35
+ def get_action
36
+ return @controller&.action_name&.to_sym
37
+ end
38
+
39
+ # Get a locally defined native serializer configuration, if one is defined.
40
+ def get_local_native_serializer_config
41
+ action = self.get_action
42
+
43
+ if action && self.action_config
44
+ # Index action should use :list serializer config if :index is not provided.
45
+ action = :list if action == :index && !self.action_config.key?(:index)
46
+
47
+ return self.action_config[action] if self.action_config[action]
30
48
  end
31
49
 
32
- # Get a locally defined configuration, if one is defined.
33
- def get_local_serializer_config
34
- action = self.get_action
50
+ # No action_config, so try singular/plural config if explicitly instructed to via @many.
51
+ return self.plural_config if @many == true && self.plural_config
52
+ return self.singular_config if @many == false && self.singular_config
35
53
 
36
- if action && self.action_config
37
- # index action should use :list serializer config if :index is not provided
38
- action = :list if action == :index && !self.action_config.key?(:index)
54
+ # Lastly, try returning the default config, or singular/plural config in that order.
55
+ return self.config || self.singular_config || self.plural_config
56
+ end
39
57
 
40
- return self.action_config[action] if self.action_config[action]
41
- end
58
+ # Helper to get a native serializer configuration from the controller.
59
+ def get_controller_native_serializer_config
60
+ return nil unless @controller
42
61
 
43
- # no action_config, so try singular/plural config
44
- return self.plural_config if @many && self.plural_config
45
- return self.singular_config if !@many && self.singular_config
62
+ if @many == true
63
+ controller_serializer = @controller.try(:native_serializer_plural_config)
64
+ elsif @many == false
65
+ controller_serializer = @controller.try(:native_serializer_singular_config)
66
+ end
67
+
68
+ return controller_serializer || @controller.try(:native_serializer_config)
69
+ end
46
70
 
47
- # lastly, try the default config
48
- return self.config
71
+ # Get a configuration passable to `serializable_hash` for the object.
72
+ def get_serializer_config
73
+ # Return a locally defined serializer config if one is defined.
74
+ if local_config = self.get_local_native_serializer_config
75
+ return local_config
49
76
  end
50
77
 
51
- # Get a configuration passable to `as_json` for the model.
52
- def get_serializer_config
53
- # return a locally defined serializer config if one is defined
54
- local_config = self.get_local_serializer_config
55
- return local_config if local_config
56
-
57
- # return a serializer config if one is defined
58
- serializer_config = @controller.send(:get_native_serializer_config)
59
- return serializer_config if serializer_config
60
-
61
- # otherwise, build a serializer config from fields
62
- fields = @controller.send(:get_fields) if @controller
63
- unless fields.blank?
64
- columns, methods = fields.partition { |f| f.to_s.in?(@model.column_names) }
65
- return {only: columns, methods: methods}
78
+ # Return a serializer config if one is defined on the controller.
79
+ if serializer_config = get_controller_native_serializer_config
80
+ return serializer_config
81
+ end
82
+
83
+ # If the config wasn't determined, build a serializer config from model fields.
84
+ fields = @controller.send(:get_fields) if @controller
85
+ if fields
86
+ if @model
87
+ columns, methods = fields.partition { |f| f.in?(@model.column_names) }
88
+ else
89
+ columns = fields
90
+ methods = []
66
91
  end
67
92
 
68
- return {}
93
+ return {only: columns, methods: methods}
69
94
  end
70
95
 
71
- # Convert the object(s) to Ruby primitives.
72
- def serialize
73
- if @object
74
- @many = @object.respond_to?(:each) if @many.nil?
75
- return @object.as_json(self.get_serializer_config)
96
+ # By default, pass an empty configuration, allowing the serialization of all columns.
97
+ return {}
98
+ end
99
+
100
+ # Convert the object (record or recordset) to Ruby primitives.
101
+ def serialize
102
+ if @object
103
+ begin
104
+ if @object.is_a?(Enumerable)
105
+ return @object.map { |r| r.serializable_hash(self.get_serializer_config) }
106
+ end
107
+ return @object.serializable_hash(self.get_serializer_config)
108
+ rescue NoMethodError
76
109
  end
77
- return nil
78
110
  end
79
111
 
80
- # Allow a serializer instance to be used as a hash directly in a nested serializer config.
81
- def [](key)
82
- unless instance_variable_defined?(:@_nested_config)
83
- @_nested_config = self.get_serializer_config
84
- end
85
- return @_nested_config[key]
112
+ # Raise an error if we cannot serialize the object.
113
+ raise RESTFramework::UnserializableError.new(@object)
114
+ end
115
+
116
+ # Allow a serializer instance to be used as a hash directly in a nested serializer config.
117
+ def [](key)
118
+ unless instance_variable_defined?(:@_nested_config)
119
+ @_nested_config = self.get_serializer_config
86
120
  end
87
- def []=(key, value)
88
- unless instance_variable_defined?(:@_nested_config)
89
- @_nested_config = self.get_serializer_config
90
- end
91
- return @_nested_config[key] = value
121
+ return @_nested_config[key]
122
+ end
123
+ def []=(key, value)
124
+ unless instance_variable_defined?(:@_nested_config)
125
+ @_nested_config = self.get_serializer_config
92
126
  end
127
+ return @_nested_config[key] = value
128
+ end
93
129
 
94
- # Allow a serializer class to be used as a hash directly in a nested serializer config.
95
- def self.[](key)
96
- unless instance_variable_defined?(:@_nested_config)
97
- @_nested_config = self.new.get_serializer_config
98
- end
99
- return @_nested_config[key]
130
+ # Allow a serializer class to be used as a hash directly in a nested serializer config.
131
+ def self.[](key)
132
+ unless instance_variable_defined?(:@_nested_config)
133
+ @_nested_config = self.new.get_serializer_config
100
134
  end
101
- def self.[]=(key, value)
102
- unless instance_variable_defined?(:@_nested_config)
103
- @_nested_config = self.new.get_serializer_config
104
- end
105
- return @_nested_config[key] = value
135
+ return @_nested_config[key]
136
+ end
137
+ def self.[]=(key, value)
138
+ unless instance_variable_defined?(:@_nested_config)
139
+ @_nested_config = self.new.get_serializer_config
106
140
  end
141
+ return @_nested_config[key] = value
142
+ end
143
+ end
144
+
145
+
146
+ # :nocov:
147
+ # Alias NativeModelSerializer -> NativeSerializer.
148
+ class RESTFramework::NativeModelSerializer < RESTFramework::NativeSerializer
149
+ def initialize(**kwargs)
150
+ super
151
+ ActiveSupport::Deprecation.warn(
152
+ <<~MSG.split("\n").join(' ')
153
+ RESTFramework::NativeModelSerializer is deprecated and will be removed in future versions of
154
+ REST Framework; you should use RESTFramework::NativeSerializer instead.
155
+ MSG
156
+ )
107
157
  end
108
158
  end
159
+ # :nocov: