oj_serializers 2.0.0 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +75 -14
- data/lib/oj_serializers/serializer.rb +150 -119
- data/lib/oj_serializers/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1a6edc7a526ccc3e6edd655070c28a9c54f2fd38646aaf36bf4db0b58363f36
|
4
|
+
data.tar.gz: 5a2541b9005c35d1f4c68a0dc142691b35199c78e0e134f6cc44ad0cd6a3a8bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ee1d2c5e850cd14548dbf1219056f61b25be85a55a83107bc6cf695b991cb52bb54605c45c88aed18e3573e53c72f22bbedbfa7a2bd066e10a17d71a1e25bd1
|
7
|
+
data.tar.gz: f8454069164b46838efecc9652e5a56fa816a07e47e7ad1983be49147c469f2c58bcdb67ab54ebb59bbcdd381abe0ee7ff7240ca282f310e581fc8dc0a275a0f
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
## Oj Serializers 2.0.2 (2023-04-02)
|
2
|
+
|
3
|
+
### Features ✨
|
4
|
+
|
5
|
+
- [Automatically remove `?` when using `transform_keys :camelize`](https://github.com/ElMassimo/oj_serializers/commit/79758a0)
|
6
|
+
|
7
|
+
### Fixes 🐞
|
8
|
+
|
9
|
+
- [Error when defining attributes with options](https://github.com/ElMassimo/oj_serializers/commit/680ab47)
|
10
|
+
|
11
|
+
## Oj Serializers 2.0.1 (2023-04-02)
|
12
|
+
|
13
|
+
### Features ✨
|
14
|
+
|
15
|
+
- [Automatically mark `id` as an identifier (rendered first)](https://github.com/ElMassimo/oj_serializers/commit/c4c6de7)
|
16
|
+
- [Fail on typos in attribute and association options](https://github.com/ElMassimo/oj_serializers/commit/afd80ac)
|
17
|
+
|
18
|
+
### Fixes 🐞
|
19
|
+
|
20
|
+
- [Aliased attributes should be sorted by the output key](https://github.com/ElMassimo/oj_serializers/commit/fc6f4c1)
|
21
|
+
|
1
22
|
## [Oj Serializers 2.0.0 (2023-03-27)](https://github.com/ElMassimo/oj_serializers/pull/9)
|
2
23
|
|
3
24
|
### Features ✨
|
data/README.md
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
Oj Serializers
|
3
3
|
<p align="center">
|
4
4
|
<a href="https://github.com/ElMassimo/oj_serializers/actions"><img alt="Build Status" src="https://github.com/ElMassimo/oj_serializers/workflows/build/badge.svg"/></a>
|
5
|
-
<a href="https://inch-ci.org/github/ElMassimo/oj_serializers"><img alt="Inline docs" src="https://inch-ci.org/github/ElMassimo/oj_serializers.svg"/></a>
|
6
5
|
<a href="https://codeclimate.com/github/ElMassimo/oj_serializers"><img alt="Maintainability" src="https://codeclimate.com/github/ElMassimo/oj_serializers/badges/gpa.svg"/></a>
|
7
6
|
<a href="https://codeclimate.com/github/ElMassimo/oj_serializers"><img alt="Test Coverage" src="https://codeclimate.com/github/ElMassimo/oj_serializers/badges/coverage.svg"/></a>
|
8
7
|
<a href="https://rubygems.org/gems/oj_serializers"><img alt="Gem Version" src="https://img.shields.io/gem/v/oj_serializers.svg?colorB=e9573f"/></a>
|
@@ -23,11 +22,15 @@ Faster JSON serializers for Ruby, built on top of the powerful [`oj`][oj] librar
|
|
23
22
|
[sugar]: https://github.com/ElMassimo/oj_serializers/blob/main/lib/oj_serializers/sugar.rb#L14
|
24
23
|
[migration guide]: https://github.com/ElMassimo/oj_serializers/blob/main/MIGRATION_GUIDE.md
|
25
24
|
[design]: https://github.com/ElMassimo/oj_serializers#design-
|
25
|
+
[associations]: https://github.com/ElMassimo/oj_serializers#associations-
|
26
|
+
[compose]: https://github.com/ElMassimo/oj_serializers#composing-serializers-
|
26
27
|
[raw_json]: https://github.com/ohler55/oj/issues/542
|
27
28
|
[trailing_commas]: https://maximomussini.com/posts/trailing-commas/
|
28
29
|
[render dsl]: https://github.com/ElMassimo/oj_serializers#render-dsl-
|
29
30
|
[sorbet]: https://sorbet.org/
|
30
31
|
[Discussion]: https://github.com/ElMassimo/oj_serializers/discussions
|
32
|
+
[TypeScript]: https://www.typescriptlang.org/
|
33
|
+
[types_from_serializers]: https://github.com/ElMassimo/types_from_serializers
|
31
34
|
|
32
35
|
## Why? 🤔
|
33
36
|
|
@@ -40,11 +43,12 @@ Learn more about [how this library achieves its performance][design].
|
|
40
43
|
|
41
44
|
## Features ⚡️
|
42
45
|
|
43
|
-
-
|
46
|
+
- Intuitive declaration syntax, supporting mixins and inheritance
|
44
47
|
- Reduced [memory allocation][benchmarks] and [improved performance][benchmarks]
|
45
|
-
-
|
48
|
+
- Generate [TypeScript interfaces automatically][types_from_serializers]
|
49
|
+
- Support for [`has_one`][associations] and [`has_many`][associations], compose with [`flat_one`][compose]
|
46
50
|
- Useful development checks to avoid typos and mistakes
|
47
|
-
-
|
51
|
+
- [Migrate easily from Active Model Serializers][migration guide]
|
48
52
|
|
49
53
|
## Installation 💿
|
50
54
|
|
@@ -67,8 +71,7 @@ attributes should be serialized.
|
|
67
71
|
class AlbumSerializer < Oj::Serializer
|
68
72
|
attributes :name, :genres
|
69
73
|
|
70
|
-
|
71
|
-
def release
|
74
|
+
attribute :release do
|
72
75
|
album.release_date.strftime('%B %d, %Y')
|
73
76
|
end
|
74
77
|
|
@@ -154,6 +157,24 @@ class AlbumsController < ApplicationController
|
|
154
157
|
end
|
155
158
|
```
|
156
159
|
|
160
|
+
<details>
|
161
|
+
<summary>Active Model Serializers style</summary>
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
require "oj_serializers/sugar" # In an initializer
|
165
|
+
|
166
|
+
class AlbumsController < ApplicationController
|
167
|
+
def show
|
168
|
+
render json: album, serializer: AlbumSerializer
|
169
|
+
end
|
170
|
+
|
171
|
+
def index
|
172
|
+
render json: albums, root: :albums, each_serializer: AlbumSerializer
|
173
|
+
end
|
174
|
+
end
|
175
|
+
```
|
176
|
+
</details>
|
177
|
+
|
157
178
|
## Rendering 🖨
|
158
179
|
|
159
180
|
Use `one` to serialize objects, and `many` to serialize enumerables:
|
@@ -169,7 +190,7 @@ Serializers can be rendered arrays, hashes, or even inside `ActiveModel::Seriali
|
|
169
190
|
by using a method in the serializer, making it very easy to combine with other
|
170
191
|
libraries and migrate incrementally.
|
171
192
|
|
172
|
-
|
193
|
+
`render` is a shortcut for `one` and `many`:
|
173
194
|
|
174
195
|
```ruby
|
175
196
|
render json: {
|
@@ -367,6 +388,51 @@ One slight variation that might make it easier to maintain in the long term is
|
|
367
388
|
to use a separate singleton service to provide the url helpers and options, and
|
368
389
|
make it available as `urls`.
|
369
390
|
|
391
|
+
### Generating TypeScript automatically 🤖
|
392
|
+
|
393
|
+
It's easy for the backend and the frontend to become out of sync. Traditionally, preventing bugs requires writing extensive integration tests.
|
394
|
+
|
395
|
+
[TypeScript] is a great tool to catch this kind of bugs and mistakes, as it can detect incorrect usages and missing fields, but writing types manually is cumbersome, and they can become stale over time, giving a false sense of confidence.
|
396
|
+
|
397
|
+
[`types_from_serializers`][types_from_serializers] extends this library to allow embedding type information, as well as inferring types from the SQL schema when available, and uses this information to automatically generate TypeScript interfaces from your serializers.
|
398
|
+
|
399
|
+
As a result, it's posible to easily detect mismatches between the backend and the frontend, as well as make the fields more discoverable and provide great autocompletion in the frontend, without having to manually write the types.
|
400
|
+
|
401
|
+
### Composing serializers 🧱
|
402
|
+
|
403
|
+
There are three options to compose serializers: inheritance, mixins, and `flat_one`.
|
404
|
+
|
405
|
+
Use `flat_one` to include all attributes from a different serializer:
|
406
|
+
|
407
|
+
```ruby
|
408
|
+
class AttachmentSerializer < BaseSerializer
|
409
|
+
identifier
|
410
|
+
|
411
|
+
class BlobSerializer < BaseSerializer
|
412
|
+
attributes :filename, :byte_size, :content_type, :created_at
|
413
|
+
end
|
414
|
+
|
415
|
+
flat_one :blob, serializer: BlobSerializer
|
416
|
+
end
|
417
|
+
```
|
418
|
+
|
419
|
+
<details>
|
420
|
+
<summary>Example Output</summary>
|
421
|
+
|
422
|
+
```ruby
|
423
|
+
{
|
424
|
+
id: 5,
|
425
|
+
filename: "image.jpg,
|
426
|
+
byte_size: 256074,
|
427
|
+
content_type: "image/jpeg",
|
428
|
+
created_at: "2022-08-04T17:25:12.637-07:00",
|
429
|
+
}
|
430
|
+
```
|
431
|
+
</details>
|
432
|
+
|
433
|
+
This is especially convenient when using [`types_from_serializers`][types_from_serializers],
|
434
|
+
as it enables automatic type inference for the included attributes.
|
435
|
+
|
370
436
|
### Memoization & local state
|
371
437
|
|
372
438
|
Serializers are designed to be stateless so that an instanced can be reused, but
|
@@ -469,10 +535,8 @@ end
|
|
469
535
|
This will change the default shortcuts (`render`, `one`, `one_if`, and `many`),
|
470
536
|
so that the serializer writes directly to JSON instead of returning a Hash.
|
471
537
|
|
472
|
-
|
473
|
-
|
474
|
-
> This was the default behavior in `oj_serializers` v1, but was replaced with
|
475
|
-
`default_format :hash` in v2.
|
538
|
+
Even when using this mode, you can still use rendered values inside arrays,
|
539
|
+
hashes, and other serializers, thanks to [the `raw_json` extensions][raw_json].
|
476
540
|
|
477
541
|
<details>
|
478
542
|
<summary>Example Output</summary>
|
@@ -558,9 +622,6 @@ so that the serializer writes directly to JSON instead of returning a Hash.
|
|
558
622
|
```
|
559
623
|
</details>
|
560
624
|
|
561
|
-
Even when using this mode, you can still use rendered values inside arrays,
|
562
|
-
hashes, and other serializers, thanks to [the `raw_json` extensions][raw_json].
|
563
|
-
|
564
625
|
## Design 📐
|
565
626
|
|
566
627
|
Unlike `ActiveModel::Serializer`, which builds a Hash that then gets encoded to
|
@@ -19,7 +19,22 @@ require 'oj_serializers/json_value'
|
|
19
19
|
class OjSerializers::Serializer
|
20
20
|
# Public: Used to validate incorrect memoization during development. Users of
|
21
21
|
# this library might add additional options as needed.
|
22
|
-
ALLOWED_INSTANCE_VARIABLES = %w[
|
22
|
+
ALLOWED_INSTANCE_VARIABLES = %w[
|
23
|
+
memo
|
24
|
+
object
|
25
|
+
options
|
26
|
+
_routes
|
27
|
+
]
|
28
|
+
|
29
|
+
KNOWN_ATTRIBUTE_OPTIONS = %i[
|
30
|
+
attribute
|
31
|
+
association
|
32
|
+
identifier
|
33
|
+
if
|
34
|
+
optional
|
35
|
+
type
|
36
|
+
serializer
|
37
|
+
].to_set
|
23
38
|
|
24
39
|
CACHE = (defined?(Rails) && Rails.cache) ||
|
25
40
|
(defined?(ActiveSupport::Cache::MemoryStore) ? ActiveSupport::Cache::MemoryStore.new : OjSerializers::Memo.new)
|
@@ -85,30 +100,40 @@ protected
|
|
85
100
|
class << self
|
86
101
|
# Public: Allows the user to specify `default_format :json`, as a simple
|
87
102
|
# way to ensure that `.one` and `.many` work as in Version 1.
|
88
|
-
|
89
|
-
|
103
|
+
#
|
104
|
+
# This setting is inherited from parent classes.
|
105
|
+
def default_format(format)
|
106
|
+
define_singleton_method(:_default_format) { format }
|
90
107
|
define_serialization_shortcuts
|
91
108
|
end
|
92
109
|
|
93
|
-
# Public: Allows to sort fields by name instead
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
110
|
+
# Public: Allows to sort fields by name instead of by definition order, or
|
111
|
+
# pass a Proc to apply a custom order.
|
112
|
+
#
|
113
|
+
# This setting is inherited from parent classes.
|
114
|
+
def sort_attributes_by(strategy)
|
115
|
+
case strategy
|
116
|
+
when :name, :definition, Proc
|
117
|
+
define_singleton_method(:_sort_attributes_by) { strategy }
|
98
118
|
else
|
99
|
-
raise ArgumentError, "Unknown sorting option: #{
|
119
|
+
raise ArgumentError, "Unknown sorting option: #{strategy.inspect}"
|
100
120
|
end
|
101
121
|
end
|
102
122
|
|
103
|
-
# Public: Allows to
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
123
|
+
# Public: Allows to transform the JSON keys to camelCase, or pass a Proc
|
124
|
+
# to apply a custom transformation.
|
125
|
+
#
|
126
|
+
# This setting is inherited from parent classes.
|
127
|
+
def transform_keys(strategy = nil, &block)
|
128
|
+
transformer = case (strategy ||= block)
|
129
|
+
when :camelize, :camel_case then ->(key) { key.camelize(:lower).chomp('?') }
|
130
|
+
when :none then nil
|
131
|
+
when Symbol then strategy.to_proc
|
132
|
+
when Proc then strategy
|
109
133
|
else
|
110
|
-
raise(ArgumentError, "Expected transform_keys to be callable, got: #{
|
134
|
+
raise(ArgumentError, "Expected transform_keys to be callable, got: #{strategy.inspect}")
|
111
135
|
end
|
136
|
+
define_singleton_method(:_transform_keys) { transformer }
|
112
137
|
end
|
113
138
|
|
114
139
|
# Public: Creates an alias for the internal object.
|
@@ -318,15 +343,15 @@ protected
|
|
318
343
|
|
319
344
|
# Public: Specify a collection of objects that should be serialized using
|
320
345
|
# the specified serializer.
|
321
|
-
def has_many(name, serializer:,
|
346
|
+
def has_many(name, serializer:, **options, &block)
|
322
347
|
define_method(name, &block) if block
|
323
|
-
add_attribute(name, association: :many,
|
348
|
+
add_attribute(name, association: :many, serializer: serializer, **options)
|
324
349
|
end
|
325
350
|
|
326
351
|
# Public: Specify an object that should be serialized using the serializer.
|
327
|
-
def has_one(name, serializer:,
|
352
|
+
def has_one(name, serializer:, **options, &block)
|
328
353
|
define_method(name, &block) if block
|
329
|
-
add_attribute(name, association: :one,
|
354
|
+
add_attribute(name, association: :one, serializer: serializer, **options)
|
330
355
|
end
|
331
356
|
# Alias: From a serializer perspective, the association type does not matter.
|
332
357
|
alias_method :belongs_to, :has_one
|
@@ -340,9 +365,8 @@ protected
|
|
340
365
|
|
341
366
|
# Public: Specify which attributes are going to be obtained from indexing
|
342
367
|
# the object.
|
343
|
-
def hash_attributes(*
|
344
|
-
|
345
|
-
method_names.each { |name| _attributes[name] = options }
|
368
|
+
def hash_attributes(*attr_names, **options)
|
369
|
+
attributes(*attr_names, **options, attribute: :hash)
|
346
370
|
end
|
347
371
|
|
348
372
|
# Public: Specify which attributes are going to be obtained from indexing
|
@@ -351,31 +375,32 @@ protected
|
|
351
375
|
# Automatically renames `_id` to `id` for Mongoid models.
|
352
376
|
#
|
353
377
|
# See ./benchmarks/document_benchmark.rb
|
354
|
-
def mongo_attributes(*
|
355
|
-
identifier(:_id, as: :id, attribute: :mongoid, **options) if
|
356
|
-
attributes(*
|
378
|
+
def mongo_attributes(*attr_names, **options)
|
379
|
+
identifier(:_id, as: :id, attribute: :mongoid, **options.slice(:if)) if attr_names.delete(:id)
|
380
|
+
attributes(*attr_names, **options, attribute: :mongoid)
|
357
381
|
end
|
358
382
|
|
359
383
|
# Public: Specify which attributes are going to be obtained by calling a
|
360
384
|
# method in the object.
|
361
|
-
def attributes(*
|
385
|
+
def attributes(*attr_names, **methods_with_options)
|
362
386
|
attr_options = methods_with_options.extract!(:if, :as, :attribute)
|
363
387
|
attr_options[:attribute] ||= :method
|
364
388
|
|
365
|
-
|
366
|
-
add_attribute(
|
389
|
+
attr_names.each do |attr_name|
|
390
|
+
add_attribute(attr_name, **attr_options)
|
367
391
|
end
|
368
392
|
|
369
|
-
methods_with_options.each do |
|
393
|
+
methods_with_options.each do |attr_name, options|
|
370
394
|
options = { as: options } if options.is_a?(Symbol)
|
371
|
-
|
395
|
+
options[:attribute] ||= attr_options[:attribute]
|
396
|
+
add_attribute(attr_name, **options)
|
372
397
|
end
|
373
398
|
end
|
374
399
|
|
375
400
|
# Public: Specify which attributes are going to be obtained by calling a
|
376
401
|
# method in the serializer.
|
377
|
-
def serializer_attributes(*
|
378
|
-
attributes(*
|
402
|
+
def serializer_attributes(*attr_names, **options)
|
403
|
+
attributes(*attr_names, **options, attribute: :serializer)
|
379
404
|
end
|
380
405
|
|
381
406
|
# Syntax Sugar: Allows to use it before a method name.
|
@@ -389,7 +414,7 @@ protected
|
|
389
414
|
options[:attribute] = :serializer
|
390
415
|
if name
|
391
416
|
define_method(name, &block) if block
|
392
|
-
add_attribute(name, options)
|
417
|
+
add_attribute(name, **options)
|
393
418
|
else
|
394
419
|
@_current_attribute_options = options
|
395
420
|
end
|
@@ -401,7 +426,7 @@ protected
|
|
401
426
|
def method_added(name)
|
402
427
|
super(name)
|
403
428
|
if @_current_attribute_options
|
404
|
-
add_attribute(name,
|
429
|
+
add_attribute(name, **@_current_attribute_options)
|
405
430
|
@_current_attribute_options = nil
|
406
431
|
end
|
407
432
|
end
|
@@ -410,45 +435,34 @@ protected
|
|
410
435
|
# calling a method in the serializer, or using `read_attribute_for_serialization`.
|
411
436
|
#
|
412
437
|
# NOTE: Prefer to use `attributes` or `serializer_attributes` explicitly.
|
413
|
-
def ams_attributes(*
|
414
|
-
|
415
|
-
define_method(
|
438
|
+
def ams_attributes(*attr_names, **options)
|
439
|
+
attr_names.each do |attr_name|
|
440
|
+
define_method(attr_name) { @object.read_attribute_for_serialization(attr_name) } unless method_defined?(attr_name)
|
416
441
|
end
|
417
|
-
attributes(*
|
442
|
+
attributes(*attr_names, **options, attribute: :serializer)
|
418
443
|
end
|
419
444
|
|
420
|
-
|
421
|
-
def _default_format
|
422
|
-
@_default_format = superclass.try(:_default_format) || :hash unless defined?(@_default_format)
|
423
|
-
@_default_format
|
424
|
-
end
|
445
|
+
private
|
425
446
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
def _sort_attributes_by
|
430
|
-
@_sort_attributes_by = superclass.try(:_sort_attributes_by) unless defined?(@_sort_attributes_by)
|
431
|
-
@_sort_attributes_by
|
432
|
-
end
|
447
|
+
def add_attribute(value_from, root: nil, as: nil, **options)
|
448
|
+
# Because it's so common, automatically mark id as an identifier.
|
449
|
+
options[:identifier] = true if value_from == :id && !options.key?(:identifier)
|
433
450
|
|
434
|
-
|
435
|
-
|
436
|
-
# This setting is inherited from parent classes.
|
437
|
-
def _transform_keys
|
438
|
-
@_transform_keys = superclass.try(:_transform_keys) unless defined?(@_transform_keys)
|
439
|
-
@_transform_keys
|
440
|
-
end
|
451
|
+
# Hash attributes could be numbers or symbols.
|
452
|
+
value_from = value_from.to_s unless options[:attribute] == :hash
|
441
453
|
|
442
|
-
|
454
|
+
# Obtain the JSON key to use for the attribute.
|
455
|
+
key = (root || as || value_from).to_s
|
443
456
|
|
444
|
-
|
445
|
-
_attributes
|
446
|
-
end
|
457
|
+
# Should be able to add "duplicate" flat associations.
|
458
|
+
key += _attributes.count.to_s if options[:association] == :flat
|
447
459
|
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
460
|
+
# Check for typos in options.
|
461
|
+
if DEV_MODE && (option, = options.find { |option, _value| !KNOWN_ATTRIBUTE_OPTIONS.include?(option) })
|
462
|
+
raise ArgumentError, "Unknown option #{option.inspect} for attribute #{value_from.inspect} in #{name}. Please check for typos."
|
463
|
+
end
|
464
|
+
|
465
|
+
_attributes[key.freeze] = { value_from: value_from, **options }.freeze
|
452
466
|
end
|
453
467
|
|
454
468
|
# Internal: Whether the object should be serialized as a collection.
|
@@ -464,19 +478,19 @@ protected
|
|
464
478
|
#
|
465
479
|
# As a result, the performance is the same as writing the most efficient
|
466
480
|
# code by hand.
|
467
|
-
def code_to_write_to_json
|
481
|
+
def code_to_write_to_json(attributes)
|
468
482
|
<<~WRITE_TO_JSON
|
469
483
|
# Public: Writes this serializer content to a provided Oj::StringWriter.
|
470
484
|
def write_to_json(writer, item, options = nil)
|
471
485
|
@object = item
|
472
486
|
@options = options
|
473
487
|
@memo.clear if defined?(@memo)
|
474
|
-
#{
|
475
|
-
|
488
|
+
#{ attributes.map { |key, options|
|
489
|
+
code_to_write_conditionally(options) {
|
476
490
|
if options[:association]
|
477
|
-
code_to_write_association(
|
491
|
+
code_to_write_association(key, options)
|
478
492
|
else
|
479
|
-
code_to_write_attribute(
|
493
|
+
code_to_write_attribute(key, options)
|
480
494
|
end
|
481
495
|
}
|
482
496
|
}.join("\n ") }#{code_to_rescue_no_method if DEV_MODE}
|
@@ -490,7 +504,7 @@ protected
|
|
490
504
|
#
|
491
505
|
# As a result, the performance is the same as writing the most efficient
|
492
506
|
# code by hand.
|
493
|
-
def code_to_render_as_hash
|
507
|
+
def code_to_render_as_hash(attributes)
|
494
508
|
<<~RENDER_AS_HASH
|
495
509
|
# Public: Writes this serializer content to a Hash.
|
496
510
|
def render_as_hash(item, options = nil)
|
@@ -498,12 +512,12 @@ protected
|
|
498
512
|
@options = options
|
499
513
|
@memo.clear if defined?(@memo)
|
500
514
|
{
|
501
|
-
#{
|
502
|
-
code_to_render_conditionally(
|
515
|
+
#{attributes.map { |key, options|
|
516
|
+
code_to_render_conditionally(options) {
|
503
517
|
if options[:association]
|
504
|
-
code_to_render_association(
|
518
|
+
code_to_render_association(key, options)
|
505
519
|
else
|
506
|
-
code_to_render_attribute(
|
520
|
+
code_to_render_attribute(key, options)
|
507
521
|
end
|
508
522
|
}
|
509
523
|
}.join(",\n ")}
|
@@ -518,7 +532,7 @@ protected
|
|
518
532
|
rescue NoMethodError => e
|
519
533
|
key = e.name.to_s.inspect
|
520
534
|
message = if respond_to?(e.name)
|
521
|
-
raise e, "Perhaps you meant to call \#{key} in \#{self.class} instead?\nTry using `
|
535
|
+
raise e, "Perhaps you meant to call \#{key} in \#{self.class} instead?\nTry using `attribute :\#{key} do` or `attribute def \#{key}`.\n\#{e.message}"
|
522
536
|
elsif @object.respond_to?(e.name)
|
523
537
|
raise e, "Perhaps you meant to call \#{key} in \#{@object.class} instead?\nTry using `attributes :\#{key}`.\n\#{e.message}"
|
524
538
|
else
|
@@ -531,8 +545,9 @@ protected
|
|
531
545
|
|
532
546
|
# Internal: Detects any include methods defined in the serializer, or defines
|
533
547
|
# one by using the lambda passed in the `if` option, if any.
|
534
|
-
def check_conditional_method(
|
535
|
-
|
548
|
+
def check_conditional_method(options)
|
549
|
+
value_from = options.fetch(:value_from)
|
550
|
+
include_method_name = "include_#{value_from}#{'?' unless value_from.to_s.ends_with?('?')}"
|
536
551
|
if render_if = options[:if]
|
537
552
|
if render_if.is_a?(Symbol)
|
538
553
|
alias_method(include_method_name, render_if)
|
@@ -548,8 +563,8 @@ protected
|
|
548
563
|
#
|
549
564
|
# NOTE: Detects any include methods defined in the serializer, or defines
|
550
565
|
# one by using the lambda passed in the `if` option, if any.
|
551
|
-
def
|
552
|
-
if (include_method_name = check_conditional_method(
|
566
|
+
def code_to_write_conditionally(options)
|
567
|
+
if (include_method_name = check_conditional_method(options))
|
553
568
|
"if #{include_method_name};#{yield};end\n"
|
554
569
|
else
|
555
570
|
yield
|
@@ -557,50 +572,52 @@ protected
|
|
557
572
|
end
|
558
573
|
|
559
574
|
# Internal: Returns the code for the association method.
|
560
|
-
def code_to_write_attribute(
|
561
|
-
|
575
|
+
def code_to_write_attribute(key, options)
|
576
|
+
value_from = options.fetch(:value_from)
|
562
577
|
|
563
|
-
case strategy = options.fetch(:attribute)
|
578
|
+
value = case (strategy = options.fetch(:attribute))
|
564
579
|
when :serializer
|
565
580
|
# Obtains the value by calling a method in the serializer.
|
566
|
-
|
581
|
+
value_from
|
567
582
|
when :method
|
568
583
|
# Obtains the value by calling a method in the object, and writes it.
|
569
|
-
"
|
584
|
+
"@object.#{value_from}"
|
570
585
|
when :hash
|
571
586
|
# Writes a Hash value to JSON, works with String or Symbol keys.
|
572
|
-
"
|
587
|
+
"@object[#{value_from.inspect}]"
|
573
588
|
when :mongoid
|
574
589
|
# Writes an Mongoid attribute to JSON, this is the fastest strategy.
|
575
|
-
"
|
590
|
+
"@object.attributes['#{value_from}']"
|
576
591
|
else
|
577
592
|
raise ArgumentError, "Unknown attribute strategy: #{strategy.inspect}"
|
578
593
|
end
|
594
|
+
|
595
|
+
"writer.push_value(#{value}, #{key.inspect})"
|
579
596
|
end
|
580
597
|
|
581
598
|
# Internal: Returns the code for the association method.
|
582
|
-
def code_to_write_association(
|
599
|
+
def code_to_write_association(key, options)
|
583
600
|
# Use a serializer method if defined, else call the association in the object.
|
584
|
-
|
585
|
-
|
601
|
+
value_from = options.fetch(:value_from)
|
602
|
+
value = method_defined?(value_from) ? value_from : "@object.#{value_from}"
|
586
603
|
serializer_class = options.fetch(:serializer)
|
587
604
|
|
588
605
|
case type = options.fetch(:association)
|
589
606
|
when :one
|
590
607
|
<<~WRITE_ONE
|
591
|
-
if
|
608
|
+
if __value = #{value}
|
592
609
|
writer.push_key('#{key}')
|
593
|
-
#{serializer_class}.write_one(writer,
|
610
|
+
#{serializer_class}.write_one(writer, __value)
|
594
611
|
end
|
595
612
|
WRITE_ONE
|
596
613
|
when :many
|
597
614
|
<<~WRITE_MANY
|
598
615
|
writer.push_key('#{key}')
|
599
|
-
#{serializer_class}.write_many(writer, #{
|
616
|
+
#{serializer_class}.write_many(writer, #{value})
|
600
617
|
WRITE_MANY
|
601
618
|
when :flat
|
602
619
|
<<~WRITE_FLAT
|
603
|
-
#{serializer_class}.write_to_json(writer, #{
|
620
|
+
#{serializer_class}.write_to_json(writer, #{value})
|
604
621
|
WRITE_FLAT
|
605
622
|
else
|
606
623
|
raise ArgumentError, "Unknown association type: #{type.inspect}"
|
@@ -612,8 +629,8 @@ protected
|
|
612
629
|
#
|
613
630
|
# NOTE: Detects any include methods defined in the serializer, or defines
|
614
631
|
# one by using the lambda passed in the `if` option, if any.
|
615
|
-
def code_to_render_conditionally(
|
616
|
-
if (include_method_name = check_conditional_method(
|
632
|
+
def code_to_render_conditionally(options)
|
633
|
+
if (include_method_name = check_conditional_method(options))
|
617
634
|
"**(#{include_method_name} ? {#{yield}} : {})"
|
618
635
|
else
|
619
636
|
yield
|
@@ -621,36 +638,39 @@ protected
|
|
621
638
|
end
|
622
639
|
|
623
640
|
# Internal: Returns the code for the attribute method.
|
624
|
-
def code_to_render_attribute(
|
625
|
-
|
626
|
-
|
641
|
+
def code_to_render_attribute(key, options)
|
642
|
+
value_from = options.fetch(:value_from)
|
643
|
+
|
644
|
+
value = case (strategy = options.fetch(:attribute))
|
627
645
|
when :serializer
|
628
|
-
|
646
|
+
value_from
|
629
647
|
when :method
|
630
|
-
"
|
648
|
+
"@object.#{value_from}"
|
631
649
|
when :hash
|
632
|
-
"
|
650
|
+
"@object[#{value_from.inspect}]"
|
633
651
|
when :mongoid
|
634
|
-
"
|
652
|
+
"@object.attributes['#{value_from}']"
|
635
653
|
else
|
636
654
|
raise ArgumentError, "Unknown attribute strategy: #{strategy.inspect}"
|
637
655
|
end
|
656
|
+
|
657
|
+
"#{key}: #{value}"
|
638
658
|
end
|
639
659
|
|
640
660
|
# Internal: Returns the code for the association method.
|
641
|
-
def code_to_render_association(
|
661
|
+
def code_to_render_association(key, options)
|
642
662
|
# Use a serializer method if defined, else call the association in the object.
|
643
|
-
|
644
|
-
|
663
|
+
value_from = options.fetch(:value_from)
|
664
|
+
value = method_defined?(value_from) ? value_from : "@object.#{value_from}"
|
645
665
|
serializer_class = options.fetch(:serializer)
|
646
666
|
|
647
667
|
case type = options.fetch(:association)
|
648
668
|
when :one
|
649
|
-
"#{key}: (
|
669
|
+
"#{key}: (__value = #{value}) ? #{serializer_class}.one_as_hash(__value) : nil"
|
650
670
|
when :many
|
651
|
-
"#{key}: #{serializer_class}.many_as_hash(#{
|
671
|
+
"#{key}: #{serializer_class}.many_as_hash(#{value})"
|
652
672
|
when :flat
|
653
|
-
"**#{serializer_class}.one_as_hash(#{
|
673
|
+
"**#{serializer_class}.one_as_hash(#{value})"
|
654
674
|
else
|
655
675
|
raise ArgumentError, "Unknown association type: #{type.inspect}"
|
656
676
|
end
|
@@ -671,25 +691,36 @@ protected
|
|
671
691
|
@instance_key ||= begin
|
672
692
|
# We take advantage of the fact that this method will always be called
|
673
693
|
# before instantiating a serializer, to apply last minute adjustments.
|
674
|
-
|
694
|
+
prepare_serializer
|
675
695
|
"#{name.underscore}_instance_#{object_id}".to_sym
|
676
696
|
end
|
677
697
|
end
|
678
698
|
|
679
699
|
# Internal: Generates write_to_json and render_as_hash methods optimized for
|
680
700
|
# the specified configuration.
|
681
|
-
def
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
701
|
+
def prepare_serializer
|
702
|
+
attributes = prepare_attributes
|
703
|
+
class_eval(code_to_write_to_json(attributes))
|
704
|
+
class_eval(code_to_render_as_hash(attributes))
|
705
|
+
end
|
706
|
+
|
707
|
+
# Internal: Returns attributes sorted and with keys transformed using
|
708
|
+
# the specified strategies.
|
709
|
+
def prepare_attributes(transform_keys: try(:_transform_keys), sort_by: try(:_sort_attributes_by))
|
710
|
+
attributes = _attributes
|
711
|
+
attributes = attributes.transform_keys(&transform_keys) if transform_keys
|
712
|
+
|
713
|
+
if sort_by == :name
|
714
|
+
sort_by = ->(name, options, _) { options[:identifier] ? "__#{name}" : name }
|
715
|
+
elsif !sort_by || sort_by == :definition
|
716
|
+
sort_by = ->(name, options, index) { options[:identifier] ? "__#{name}" : "zzz#{index}" }
|
686
717
|
end
|
687
|
-
|
688
|
-
|
718
|
+
|
719
|
+
attributes.sort_by.with_index { |(name, options), index| sort_by.call(name, options, index) }.to_h
|
689
720
|
end
|
690
721
|
end
|
691
722
|
|
692
|
-
|
723
|
+
default_format :hash
|
693
724
|
end
|
694
725
|
|
695
726
|
Oj::Serializer = OjSerializers::Serializer unless defined?(Oj::Serializer)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oj_serializers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maximo Mussini
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oj
|
@@ -68,7 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: '0'
|
70
70
|
requirements: []
|
71
|
-
rubygems_version: 3.
|
71
|
+
rubygems_version: 3.3.7
|
72
72
|
signing_key:
|
73
73
|
specification_version: 4
|
74
74
|
summary: A lighter JSON serializer for Ruby Objects in Rails. Easily migrate away
|