• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gregschmit / rails-rest-framework / 4002900794

pending completion
4002900794

push

github

GitHub
Bump commonmarker from 0.23.6 to 0.23.7 in /docs

807 of 890 relevant lines covered (90.67%)

74.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.66
/lib/rest_framework/serializers.rb
1
# The base serializer defines the interface for all REST Framework serializers.
2
class RESTFramework::BaseSerializer
1✔
3
  # Add `object` accessor to be compatible with `ActiveModel::Serializer`.
4
  attr_accessor :object
1✔
5

6
  # Accept/ignore `*args` to be compatible with the `ActiveModel::Serializer#initialize` signature.
7
  def initialize(object=nil, *args, controller: nil, **kwargs)
1✔
8
    @object = object
115✔
9
    @controller = controller
115✔
10
  end
11

12
  # The primary interface for extracting a native Ruby types. This works both for records and
13
  # collections. We accept and ignore `*args` for compatibility with `active_model_serializers`.
14
  def serialize(*args)
1✔
15
    raise NotImplementedError
1✔
16
  end
17

18
  # Synonym for `serialize` for compatibility with `active_model_serializers`.
19
  def serializable_hash(*args)
1✔
20
    return self.serialize(*args)
7✔
21
  end
22

23
  # For compatibility with `active_model_serializers`.
24
  def self.cache_enabled?
1✔
25
    return false
6✔
26
  end
27

28
  # For compatibility with `active_model_serializers`.
29
  def self.fragment_cache_enabled?
1✔
30
    return false
6✔
31
  end
32

33
  # For compatibility with `active_model_serializers`.
34
  def associations(*args, **kwargs)
1✔
35
    return []
6✔
36
  end
37
end
38

39
# This serializer uses `.serializable_hash` to convert objects to Ruby primitives (with the
40
# top-level being either an array or a hash).
41
class RESTFramework::NativeSerializer < RESTFramework::BaseSerializer
1✔
42
  class_attribute :config
1✔
43
  class_attribute :singular_config
1✔
44
  class_attribute :plural_config
1✔
45
  class_attribute :action_config
1✔
46

47
  # Accept/ignore `*args` to be compatible with the `ActiveModel::Serializer#initialize` signature.
48
  def initialize(object=nil, *args, many: nil, model: nil, **kwargs)
1✔
49
    super(object, *args, **kwargs)
114✔
50

51
    if many.nil?
114✔
52
      # Determine if we are dealing with many objects or just one.
53
      @many = @object.is_a?(Enumerable)
112✔
54
    else
55
      @many = many
2✔
56
    end
57

58
    # Determine model either explicitly, or by inspecting @object or @controller.
59
    @model = model
114✔
60
    @model ||= @object.class if @object.is_a?(ActiveRecord::Base)
114✔
61
    @model ||= @object[0].class if
62
      @many && @object.is_a?(Enumerable) && @object.is_a?(ActiveRecord::Base)
114✔
63

64
    @model ||= @controller.class.get_model if @controller
114✔
65
  end
66

67
  # Get controller action, if possible.
68
  def get_action
1✔
69
    return @controller&.action_name&.to_sym
119✔
70
  end
71

72
  # Get a locally defined native serializer configuration, if one is defined.
73
  def get_local_native_serializer_config
1✔
74
    action = self.get_action
119✔
75

76
    if action && self.action_config
119✔
77
      # Index action should use :list serializer config if :index is not provided.
78
      action = :list if action == :index && !self.action_config.key?(:index)
15✔
79

80
      return self.action_config[action] if self.action_config[action]
15✔
81
    end
82

83
    # No action_config, so try singular/plural config if explicitly instructed to via @many.
84
    return self.plural_config if @many == true && self.plural_config
108✔
85
    return self.singular_config if @many == false && self.singular_config
106✔
86

87
    # Lastly, try returning the default config, or singular/plural config in that order.
88
    return self.config || self.singular_config || self.plural_config
97✔
89
  end
90

91
  # Get a native serializer configuration from the controller.
92
  def get_controller_native_serializer_config
1✔
93
    return nil unless @controller
88✔
94

95
    if @many == true
88✔
96
      controller_serializer = @controller.class.try(:native_serializer_plural_config)
41✔
97
    elsif @many == false
47✔
98
      controller_serializer = @controller.class.try(:native_serializer_singular_config)
47✔
99
    end
100

101
    return controller_serializer || @controller.class.try(:native_serializer_config)
88✔
102
  end
103

104
  # Filter a single subconfig for specific keys. By default, keys from `fields` are removed from the
105
  # provided `subcfg`. There are two (mutually exclusive) options to adjust the behavior:
106
  #
107
  #  `add`: Add any `fields` to the `subcfg` which aren't already in the `subcfg`.
108
  #  `only`: Remove any values found in the `subcfg` not in `fields`.
109
  def self.filter_subcfg(subcfg, fields:, add: false, only: false)
1✔
110
    raise "`add` and `only` conflict with one another" if add && only
19✔
111

112
    # Don't process nil `subcfg`s.
113
    return subcfg unless subcfg
19✔
114

115
    if subcfg.is_a?(Array)
10✔
116
      subcfg = subcfg.map(&:to_sym)
6✔
117

118
      if add
6✔
119
        # Only add fields which are not already included.
120
        subcfg += fields - subcfg
1✔
121
      elsif only
5✔
122
        subcfg.select! { |c| c.in?(fields) }
9✔
123
      else
124
        subcfg -= fields
3✔
125
      end
126
    elsif subcfg.is_a?(Hash)
4✔
127
      subcfg = subcfg.symbolize_keys
4✔
128

129
      if add
4✔
130
        # Add doesn't make sense in a hash context since we wouldn't know the values.
131
      elsif only
4✔
132
        subcfg.select! { |k, _v| k.in?(fields) }
3✔
133
      else
134
        subcfg.reject! { |k, _v| k.in?(fields) }
3✔
135
      end
136
    else  # Subcfg is a single element (assume string/symbol).
137
      subcfg = subcfg.to_sym
×
138

139
      if add
×
140
        subcfg = subcfg.in?(fields) ? fields : [subcfg, *fields]
×
141
      elsif only
×
142
        subcfg = subcfg.in?(fields) ? subcfg : []
×
143
      else
144
        subcfg = subcfg.in?(fields) ? [] : subcfg
×
145
      end
146
    end
147

148
    return subcfg
10✔
149
  end
150

151
  # Filter out configuration properties based on the :except/:only query parameters.
152
  def filter_from_request(cfg)
1✔
153
    return cfg unless @controller
119✔
154

155
    except_param = @controller.class.try(:native_serializer_except_query_param)
103✔
156
    only_param = @controller.class.try(:native_serializer_only_query_param)
103✔
157
    if except_param && except = @controller.request.query_parameters[except_param].presence
103✔
158
      if except = except.split(",").map(&:strip).map(&:to_sym).presence
3✔
159
        # Filter `only`, `except` (additive), `include`, `methods`, and `serializer_methods`.
160
        if cfg[:only]
3✔
161
          cfg[:only] = self.class.filter_subcfg(cfg[:only], fields: except)
2✔
162
        elsif cfg[:except]
1✔
163
          cfg[:except] = self.class.filter_subcfg(cfg[:except], fields: except, add: true)
1✔
164
        else
165
          cfg[:except] = except
×
166
        end
167

168
        cfg[:include] = self.class.filter_subcfg(cfg[:include], fields: except)
3✔
169
        cfg[:methods] = self.class.filter_subcfg(cfg[:methods], fields: except)
3✔
170
        cfg[:serializer_methods] = self.class.filter_subcfg(
3✔
171
          cfg[:serializer_methods], fields: except
172
        )
173
      end
174
    elsif only_param && only = @controller.request.query_parameters[only_param].presence
100✔
175
      if only = only.split(",").map(&:strip).map(&:to_sym).presence
2✔
176
        # Filter `only`, `include`, and `methods`. Adding anything to `except` is not needed,
177
        # because any configuration there takes precedence over `only`.
178
        if cfg[:only]
2✔
179
          cfg[:only] = self.class.filter_subcfg(cfg[:only], fields: only, only: true)
1✔
180
        else
181
          cfg[:only] = only
1✔
182
        end
183

184
        cfg[:include] = self.class.filter_subcfg(cfg[:include], fields: only, only: true)
2✔
185
        cfg[:methods] = self.class.filter_subcfg(cfg[:methods], fields: only, only: true)
2✔
186
        cfg[:serializer_methods] = self.class.filter_subcfg(
2✔
187
          cfg[:serializer_methods], fields: only, only: true
188
        )
189
      end
190
    end
191

192
    return cfg
103✔
193
  end
194

195
  # Get the associations limit from the controller.
196
  def _get_associations_limit
1✔
197
    return @_get_associations_limit if defined?(@_get_associations_limit)
132✔
198

199
    limit = @controller.class.native_serializer_associations_limit
44✔
200

201
    # Extract the limit from the query parameters if it's set.
202
    if query_param = @controller.class.native_serializer_associations_limit_query_param
44✔
203
      if @controller.request.query_parameters.key?(query_param)
44✔
204
        query_limit = @controller.request.query_parameters[query_param].to_i
×
205
        if query_limit > 0
×
206
          limit = query_limit
×
207
        else
208
          limit = nil
×
209
        end
210
      end
211
    end
212

213
    return @_get_associations_limit = limit
44✔
214
  end
215

216
  # Get a serializer configuration from the controller. `@controller` and `@model` must be set.
217
  def _get_controller_serializer_config(fields)
1✔
218
    columns = []
84✔
219
    includes = {}
84✔
220
    methods = []
84✔
221
    serializer_methods = {}
84✔
222
    fields.each do |f|
84✔
223
      if f.in?(@model.column_names)
793✔
224
        columns << f
561✔
225
      elsif ref = @model.reflections[f]
232✔
226
        sub_columns = []
232✔
227
        sub_methods = []
232✔
228
        @controller.class.get_field_config(f)[:sub_fields].each do |sf|
232✔
229
          if !ref.polymorphic? && sf.in?(ref.klass.column_names)
453✔
230
            sub_columns << sf
431✔
231
          else
232
            sub_methods << sf
22✔
233
          end
234
        end
235
        sub_config = {only: sub_columns, methods: sub_methods}
232✔
236

237
        # Apply certain rules regarding collection associations.
238
        if ref.collection?
232✔
239
          # If we need to limit the number of serialized association records, then dynamically add a
240
          # serializer method to do so.
241
          if limit = self._get_associations_limit
132✔
242
            method_name = "__rrf_limit_method_#{f}"
66✔
243
            serializer_methods[method_name] = f
66✔
244
            self.define_singleton_method(method_name) do |record|
66✔
245
              next record.send(f).limit(limit).as_json(**sub_config)
108✔
246
            end
247
          else
248
            includes[f] = sub_config
66✔
249
          end
250

251
          # If we need to include the association count, then add it here.
252
          if @controller.class.native_serializer_include_associations_count
132✔
253
            method_name = "__rrf_count_method_#{f}"
66✔
254
            serializer_methods[method_name] = "#{f}.count"
66✔
255
            self.define_singleton_method(method_name) do |record|
66✔
256
              next record.send(f).count
108✔
257
            end
258
          end
259
        else
260
          includes[f] = sub_config
100✔
261
        end
262
      elsif @model.method_defined?(f)
×
263
        methods << f
×
264
      end
265
    end
266

267
    return {
268
      only: columns, include: includes, methods: methods, serializer_methods: serializer_methods
84✔
269
    }
270
  end
271

272
  # Get the raw serializer config. Use `deep_dup` on any class mutables (array, hash, etc) to avoid
273
  # mutating class state.
274
  def _get_raw_serializer_config
1✔
275
    # Return a locally defined serializer config if one is defined.
276
    if local_config = self.get_local_native_serializer_config
119✔
277
      return local_config.deep_dup
31✔
278
    end
279

280
    # Return a serializer config if one is defined on the controller.
281
    if serializer_config = self.get_controller_native_serializer_config
88✔
282
      return serializer_config.deep_dup
4✔
283
    end
284

285
    # If the config wasn't determined, build a serializer config from controller fields.
286
    if @model && fields = @controller&.get_fields(fallback: true)
84✔
287
      return self._get_controller_serializer_config(fields.deep_dup)
84✔
288
    end
289

290
    # By default, pass an empty configuration, using the default Rails serializer.
291
    return {}
×
292
  end
293

294
  # Get a configuration passable to `serializable_hash` for the object, filtered if required.
295
  def get_serializer_config
1✔
296
    return filter_from_request(self._get_raw_serializer_config)
119✔
297
  end
298

299
  # Serialize a single record and merge results of `serializer_methods`.
300
  def _serialize(record, config, serializer_methods)
1✔
301
    # Ensure serializer_methods is either falsy, or a hash.
302
    if serializer_methods && !serializer_methods.is_a?(Hash)
251✔
303
      serializer_methods = [serializer_methods].flatten.map { |m| [m, m] }.to_h
18✔
304
    end
305

306
    # Merge serialized record with any serializer method results.
307
    return record.serializable_hash(config).merge(
251✔
308
      serializer_methods&.map { |m, k| [k.to_sym, self.send(m, record)] }.to_h,
225✔
309
    )
310
  end
311

312
  def serialize(*args)
1✔
313
    config = self.get_serializer_config
111✔
314
    serializer_methods = config.delete(:serializer_methods)
111✔
315

316
    if @object.respond_to?(:to_ary)
111✔
317
      return @object.map { |r| self._serialize(r, config, serializer_methods) }
244✔
318
    end
319

320
    return self._serialize(@object, config, serializer_methods)
59✔
321
  end
322

323
  # Allow a serializer instance to be used as a hash directly in a nested serializer config.
324
  def [](key)
1✔
325
    @_nested_config ||= self.get_serializer_config
24✔
326
    return @_nested_config[key]
24✔
327
  end
328

329
  def []=(key, value)
1✔
330
    @_nested_config ||= self.get_serializer_config
×
331
    return @_nested_config[key] = value
×
332
  end
333

334
  # Allow a serializer class to be used as a hash directly in a nested serializer config.
335
  def self.[](key)
1✔
336
    @_nested_config ||= self.new.get_serializer_config
3✔
337
    return @_nested_config[key]
3✔
338
  end
339

340
  def self.[]=(key, value)
1✔
341
    @_nested_config ||= self.new.get_serializer_config
×
342
    return @_nested_config[key] = value
×
343
  end
344
end
345

346
# This is a helper factory to wrap an ActiveModelSerializer to provide a `serialize` method which
347
# accepts both collections and individual records. Use `.for` to build adapters.
348
class RESTFramework::ActiveModelSerializerAdapterFactory
1✔
349
  def self.for(active_model_serializer)
1✔
350
    return Class.new(active_model_serializer) do
2✔
351
      def serialize
2✔
352
        if self.object.respond_to?(:to_ary)
2✔
353
          return self.object.map { |r| self.class.superclass.new(r).serializable_hash }
2✔
354
        end
355

356
        return self.serializable_hash
1✔
357
      end
358
    end
359
  end
360
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc