alba 2.0.1 → 2.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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +1 -1
- data/.rubocop.yml +6 -1
- data/CHANGELOG.md +36 -3
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +3 -3
- data/README.md +102 -34
- data/alba.gemspec +1 -1
- data/benchmark/collection.rb +9 -147
- data/benchmark/prep.rb +82 -0
- data/benchmark/single_resource.rb +3 -154
- data/docs/migrate_from_active_model_serializers.md +2 -2
- data/docs/rails.md +1 -1
- data/lib/alba/association.rb +6 -4
- data/lib/alba/conditional_attribute.rb +1 -1
- data/lib/alba/layout.rb +2 -2
- data/lib/alba/railtie.rb +8 -0
- data/lib/alba/resource.rb +38 -32
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +29 -4
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c68ec07a949642b11dfd36b3bf748249d0d725178ece9dd331e373fc2339cb8a
|
4
|
+
data.tar.gz: 7704431d69f96b88d9632c114dae90cfceae018c8372c8736c091efd2050b65a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98ca1b6fa7c6851f3a69d6d1cb54cd782a3142fccfb33391eb08cbc093ad691496a8fdb9e93e5ce89543032a0003ab8b60022ca8bbbba81c73de249b571a3fa6
|
7
|
+
data.tar.gz: 3756fe21da92358031a722d83b7169ef81e8749ccf6430bef577dfaac7ab4a80e68c07c6e0205497e54692f74b16e66aabad390db6eda09e486a732387541630
|
data/.github/workflows/main.yml
CHANGED
@@ -8,7 +8,7 @@ jobs:
|
|
8
8
|
fail-fast: false
|
9
9
|
matrix:
|
10
10
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
11
|
-
ruby: [2.
|
11
|
+
ruby: [2.7, 3.0, 3.1, 3.2, head, jruby, truffleruby]
|
12
12
|
gemfile: [all, without_active_support, without_oj]
|
13
13
|
exclude:
|
14
14
|
- os: windows-latest
|
data/.rubocop.yml
CHANGED
@@ -16,7 +16,7 @@ AllCops:
|
|
16
16
|
- 'script/**/*.rb'
|
17
17
|
NewCops: enable
|
18
18
|
EnabledByDefault: true
|
19
|
-
TargetRubyVersion: 2.
|
19
|
+
TargetRubyVersion: 2.7
|
20
20
|
|
21
21
|
# Items in Gemfile is dev dependencies and we don't have to specify versions.
|
22
22
|
Bundler/GemVersion:
|
@@ -58,6 +58,11 @@ Metrics/ParameterLists:
|
|
58
58
|
Exclude:
|
59
59
|
- 'test/**/*.rb'
|
60
60
|
|
61
|
+
# Putting extra empty line is not valuable in test
|
62
|
+
# We prefer shorter test code
|
63
|
+
Minitest/EmptyLineBeforeAssertionMethods:
|
64
|
+
Enabled: false
|
65
|
+
|
61
66
|
# We need to eval resource code to test errors on resource classes
|
62
67
|
Security/Eval:
|
63
68
|
Exclude:
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [2.2.0] 2023-02-17
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Rails integration to set default inflector [#298](https://github.com/okuramasafumi/alba/pull/298)
|
14
|
+
|
15
|
+
### Fixed
|
16
|
+
|
17
|
+
- Fix cascade not working with association and inheritance [#300](https://github.com/okuramasafumi/alba/pull/300)
|
18
|
+
|
19
|
+
### Removed
|
20
|
+
|
21
|
+
- Drop support of Ruby 2.6
|
22
|
+
|
23
|
+
## [2.1.0] 2022-12-03
|
24
|
+
|
25
|
+
### Added
|
26
|
+
|
27
|
+
- Add `select` method for filtering attributes [#270](https://github.com/okuramasafumi/alba/pull/270)
|
28
|
+
- Allow ConditionalAttribute with 2-arity proc to reject nil attributes [#273](https://github.com/okuramasafumi/alba/pull/273)
|
29
|
+
|
30
|
+
### Fixed
|
31
|
+
|
32
|
+
- Add support for proc resource in one polymorphic associations [#281](https://github.com/okuramasafumi/alba/pull/281)
|
33
|
+
|
34
|
+
### Deprecated
|
35
|
+
|
36
|
+
- Deprecate `inference` related methods in favor of a unified `inflector` interface.
|
37
|
+
Deprecated methods are: `Alba.enable_inference!`, `Alba.disable_inference!`, and `Alba.inferring`.
|
38
|
+
Use `Alba.inflector = :active_support/:dry` or `Alba.inflector = SomeInflector` to enable.
|
39
|
+
Use `Alba.inflector = nil` to disable.
|
40
|
+
Use `Alba.inflector` to check if enabled.
|
41
|
+
|
9
42
|
## [2.0.1] 2022-11-02
|
10
43
|
|
11
44
|
### Fix
|
@@ -17,9 +50,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
17
50
|
|
18
51
|
### Breaking changes
|
19
52
|
|
20
|
-
- All Hash-related methods now return String
|
21
|
-
This affects all users, but you can use `deep_symbolize_keys` in Rails environment if you prefer Symbol keys
|
22
|
-
Some DSLs that take key argument such as `on_nil` and `on_error`, are also affected
|
53
|
+
- All Hash-related methods now return String keys instead of Symbol keys.
|
54
|
+
This affects all users, but you can use `deep_symbolize_keys` in Rails environment if you prefer Symbol keys, or `with_indifferent_access` to support both String and Symbol keys.
|
55
|
+
Some DSLs that take key argument such as `on_nil` and `on_error`, are also affected.
|
23
56
|
- Remove deprecated methods: `Resource#to_hash`, `Resource.ignoring`, `Alba.on_nil`, `Alba.on_error`, `Alba.enable_root_key_transformation!` and `Alba.disable_root_key_transformation!`
|
24
57
|
- If using `transform_keys`, the default inflector is no longer set by default [d02245c8](https://github.com/okuramasafumi/alba/commit/d02245c87e9df303cb20e354a81e5457ea460bdd#diff-ecd8c835d2390b8cb89e7ff75e599f0c15cdbe18c30981d6090f4a515566686f)
|
25
58
|
To retain this functionality in Rails, add an initializer with the following:
|
data/CONTRIBUTING.md
CHANGED
@@ -27,4 +27,4 @@ You're more than welcomed to submit a Pull Request! You are expected to follow t
|
|
27
27
|
## Documents
|
28
28
|
|
29
29
|
* [code of conduct](https://github.com/okuramasafumi/alba/blob/master/CODE_OF_CONDUCT.md) for all contributors
|
30
|
-
* [hacking document](https://github.com/okuramasafumi/alba/blob/
|
30
|
+
* [hacking document](https://github.com/okuramasafumi/alba/blob/main/HACKING.md) for those who want to know the internal of Alba
|
data/Gemfile
CHANGED
@@ -6,12 +6,12 @@ gemspec
|
|
6
6
|
gem 'activesupport', require: false # For backend
|
7
7
|
gem 'dry-inflector', require: false # For inflection
|
8
8
|
gem 'ffaker', require: false # For testing
|
9
|
-
gem 'inch', require: false # For inline documents
|
10
9
|
gem 'minitest', '~> 5.14' # For test
|
10
|
+
gem 'railties', require: false # For Rails integration testing
|
11
11
|
gem 'rake', '~> 13.0' # For test and automation
|
12
12
|
gem 'rubocop', '>= 0.79.0', require: false # For lint
|
13
|
-
gem 'rubocop-minitest', '
|
14
|
-
gem 'rubocop-performance', '
|
13
|
+
gem 'rubocop-minitest', '>= 0.25.0', require: false # For lint
|
14
|
+
gem 'rubocop-performance', '>= 1.15.0', require: false # For lint
|
15
15
|
gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
16
16
|
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
17
17
|
gem 'ruby-lsp', require: false # For language server
|
data/README.md
CHANGED
@@ -23,6 +23,16 @@ If you have feature requests or interesting ideas, join us with [Ideas](https://
|
|
23
23
|
|
24
24
|
If you want to know more about Alba, there's a [screencast](https://hanamimastery.com/episodes/21-serialization-with-alba) created by Sebastian from [Hanami Mastery](https://hanamimastery.com/). It covers basic features of Alba and how to use it in Hanami.
|
25
25
|
|
26
|
+
## What users say about Alba
|
27
|
+
|
28
|
+
> Alba is a well-maintained JSON serialization engine, for Ruby, JRuby, and TruffleRuby implementations, and what I like in this gem - except of its speed, is the easiness of use, no dependencies and the fact it plays well with any Ruby application!
|
29
|
+
|
30
|
+
[Hanami Mastery by Seb Wilgosz](https://hanamimastery.com/episodes/21-serialization-with-alba)
|
31
|
+
|
32
|
+
> Alba is more feature-rich and pretty fast, too
|
33
|
+
|
34
|
+
[Gemfile of dreams by Evil Martians](https://evilmartians.com/chronicles/gemfile-of-dreams-libraries-we-use-to-build-rails-apps)
|
35
|
+
|
26
36
|
## Why Alba?
|
27
37
|
|
28
38
|
Because it's fast, easy and feature rich!
|
@@ -37,7 +47,7 @@ Alba is easy to use because there are only a few methods to remember. It's also
|
|
37
47
|
|
38
48
|
### Feature rich
|
39
49
|
|
40
|
-
While Alba's core is simple, it provides additional features when you need them, For example, Alba provides [a way to control circular associations](#circular-associations-control), [
|
50
|
+
While Alba's core is simple, it provides additional features when you need them, For example, Alba provides [a way to control circular associations](#circular-associations-control), [root key and association resource name inference](#root-key-and-association-resource-name-inference) and [supports layouts](#layout).
|
41
51
|
|
42
52
|
## Installation
|
43
53
|
|
@@ -57,7 +67,7 @@ Or install it yourself as:
|
|
57
67
|
|
58
68
|
## Supported Ruby versions
|
59
69
|
|
60
|
-
Alba supports CRuby 2.
|
70
|
+
Alba supports CRuby 2.7 and higher and latest JRuby and TruffleRuby.
|
61
71
|
|
62
72
|
## Documentation
|
63
73
|
|
@@ -68,10 +78,9 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramas
|
|
68
78
|
* Conditional attributes and associations
|
69
79
|
* Selectable backend
|
70
80
|
* Key transformation
|
71
|
-
* Root key inference
|
81
|
+
* Root key and association resource name inference
|
72
82
|
* Error handling
|
73
83
|
* Nil handling
|
74
|
-
* Resource name inflection based on association name
|
75
84
|
* Circular associations control
|
76
85
|
* [Experimental] Types for validation and conversion
|
77
86
|
* Layout
|
@@ -113,19 +122,33 @@ You can consider setting a backend with Symbol as a shortcut to set encoder.
|
|
113
122
|
|
114
123
|
#### Inference configuration
|
115
124
|
|
116
|
-
You can enable inference feature using `
|
125
|
+
You can enable the inference feature using the `Alba.inflector = SomeInflector` API. For example, in a Rails initializer:
|
117
126
|
|
118
127
|
```ruby
|
119
|
-
Alba.
|
128
|
+
Alba.inflector = :active_support
|
120
129
|
```
|
121
130
|
|
122
|
-
You can choose which inflector Alba uses for inference. Possible
|
131
|
+
You can choose which inflector Alba uses for inference. Possible options are:
|
123
132
|
|
124
133
|
- `:active_support` for `ActiveSupport::Inflector`
|
125
134
|
- `:dry` for `Dry::Inflector`
|
126
|
-
- any object which
|
135
|
+
- any object which conforms to the protocol (see [below](#custom-inflector))
|
136
|
+
|
137
|
+
To disable inference, set the `inflector` to `nil`:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
Alba.inflector = nil
|
141
|
+
```
|
127
142
|
|
128
|
-
|
143
|
+
To check if inference is enabled etc, inspect the return value of `inflector`:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
if Alba.inflector == nil
|
147
|
+
puts "inflector not set"
|
148
|
+
else
|
149
|
+
puts "inflector is set to #{Alba.inflector}"
|
150
|
+
end
|
151
|
+
```
|
129
152
|
|
130
153
|
### Simple serialization with root key
|
131
154
|
|
@@ -157,7 +180,7 @@ end
|
|
157
180
|
|
158
181
|
user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
|
159
182
|
UserResource.new(user).serialize
|
160
|
-
# =>
|
183
|
+
# => '{"user":{"id":1,"name":"Masafumi OKURA","name_with_email":"Masafumi OKURA: masafumi@example.com"}}'
|
161
184
|
```
|
162
185
|
|
163
186
|
You can define instance methods on resources so that you can use it as attribute name in `attributes`.
|
@@ -184,7 +207,7 @@ This even works with users collection.
|
|
184
207
|
user1 = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
|
185
208
|
user2 = User.new(2, 'Test User', 'test@example.com')
|
186
209
|
UserResource.new([user1, user2]).serialize
|
187
|
-
# =>
|
210
|
+
# => '{"users":[{"id":1,"name":"Masafumi OKURA","name_with_email":"Masafumi OKURA: masafumi@example.com"},{"id":2,"name":"Test User","name_with_email":"Test User: test@example.com"}]}'
|
188
211
|
```
|
189
212
|
|
190
213
|
If you have a simple case where you want to change only the name, you can use the Symbol to Proc shortcut:
|
@@ -210,8 +233,8 @@ class UserResource
|
|
210
233
|
end
|
211
234
|
|
212
235
|
user = User.new(1, 'Masa', 'test@example.com')
|
213
|
-
UserResource.new(user).serialize # =>
|
214
|
-
UserResource.new(user, params: {upcase: true}).serialize # =>
|
236
|
+
UserResource.new(user).serialize # => '{"name":"Masa"}'
|
237
|
+
UserResource.new(user, params: {upcase: true}).serialize # => '{"name":"MASA"}'
|
215
238
|
```
|
216
239
|
|
217
240
|
### Serialization with associations
|
@@ -368,9 +391,11 @@ UserResource.new(user).serialize
|
|
368
391
|
# => '{"id":1,"my_articles":[{"title":"Hello World!"}]}'
|
369
392
|
```
|
370
393
|
|
371
|
-
You can omit resource option if you enable Alba's inference feature.
|
394
|
+
You can omit the resource option if you enable Alba's [inference](#inference-configuration) feature.
|
372
395
|
|
373
396
|
```ruby
|
397
|
+
Alba.inflector = :active_support
|
398
|
+
|
374
399
|
class UserResource
|
375
400
|
include Alba::Resource
|
376
401
|
|
@@ -390,7 +415,7 @@ class UserResource
|
|
390
415
|
|
391
416
|
attributes :id
|
392
417
|
|
393
|
-
many :articles, ->(article) { article.with_comment? ? ArticleWithCommentResource : ArticleResource }
|
418
|
+
many :articles, resource: ->(article) { article.with_comment? ? ArticleWithCommentResource : ArticleResource }
|
394
419
|
end
|
395
420
|
```
|
396
421
|
|
@@ -564,13 +589,19 @@ end
|
|
564
589
|
Foo = Struct.new(:bar, :baz)
|
565
590
|
foo = Foo.new(1, 2)
|
566
591
|
FooResource.new(foo).serialize # => '{"foo":{"bar":1}}'
|
567
|
-
ExtendedFooResource.new(foo).serialize # => '{"
|
592
|
+
ExtendedFooResource.new(foo).serialize # => '{"foofoo":{"bar":1,"baz":2}}'
|
568
593
|
```
|
569
594
|
|
570
595
|
In this example we add `baz` attribute and change `root_key`. This way, you can extend existing resource classes just like normal OOP. Don't forget that when your inheritance structure is too deep it'll become difficult to modify existing classes.
|
571
596
|
|
572
597
|
### Filtering attributes
|
573
598
|
|
599
|
+
Filtering attributes can be done in two ways - with `attributes` and `select`. They have different semantics and usage.
|
600
|
+
|
601
|
+
`select` is a new and more intuitive API, so generally it's recommended to use `select`.
|
602
|
+
|
603
|
+
#### Filtering attributes with `attributes`
|
604
|
+
|
574
605
|
You can filter out certain attributes by overriding `attributes` instance method. This is useful when you want to customize existing resource with inheritance.
|
575
606
|
|
576
607
|
You can access raw attributes via `super` call. It returns a Hash whose keys are the name of the attribute and whose values are the body. Usually you need only keys to filter out, like below.
|
@@ -598,19 +629,52 @@ class RestrictedFooResource < GenericFooResource
|
|
598
629
|
end
|
599
630
|
end
|
600
631
|
|
632
|
+
foo = Foo.new(1, 'my foo', 'body')
|
633
|
+
|
601
634
|
RestrictedFooResource.new(foo).serialize
|
602
635
|
# => '{"name":"my foo"}'
|
603
636
|
```
|
604
637
|
|
605
|
-
|
638
|
+
#### Filtering attributes with `select`
|
606
639
|
|
607
|
-
|
608
|
-
* install it
|
609
|
-
* use a [custom inflector](#custom-inflector)
|
640
|
+
When you want to filter attributes based on more complex logic, you can use `select` instance method. `select` takes two parameters, the name of an attribute and the value of an attribute. If it returns false that attribute is rejected.
|
610
641
|
|
611
|
-
|
642
|
+
```ruby
|
643
|
+
class Foo
|
644
|
+
attr_accessor :id, :name, :body
|
645
|
+
|
646
|
+
def initialize(id, name, body)
|
647
|
+
@id = id
|
648
|
+
@name = name
|
649
|
+
@body = body
|
650
|
+
end
|
651
|
+
end
|
652
|
+
|
653
|
+
class GenericFooResource
|
654
|
+
include Alba::Resource
|
655
|
+
|
656
|
+
attributes :id, :name, :body
|
657
|
+
end
|
658
|
+
|
659
|
+
class RestrictedFooResource < GenericFooResource
|
660
|
+
def select(_key, value)
|
661
|
+
!value.nil?
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
foo = Foo.new(1, nil, 'body')
|
666
|
+
|
667
|
+
RestrictedFooResource.new(foo).serialize
|
668
|
+
# => '{"id":1,"body":"body"}'
|
669
|
+
```
|
670
|
+
|
671
|
+
### Key transformation
|
672
|
+
|
673
|
+
If you have [inference](#inference-configuration) enabled, you can use the `transform_keys` DSL to transform attribute keys.
|
612
674
|
|
613
675
|
```ruby
|
676
|
+
Alba.inflector = :active_support
|
677
|
+
|
614
678
|
class User
|
615
679
|
attr_reader :id, :first_name, :last_name
|
616
680
|
|
@@ -646,12 +710,12 @@ Possible values for `transform_keys` argument are:
|
|
646
710
|
|
647
711
|
You can also transform root key when:
|
648
712
|
|
649
|
-
* `Alba.
|
713
|
+
* `Alba.inflector` is set
|
650
714
|
* `root_key!` is called in Resource class
|
651
715
|
* `root` option of `transform_keys` is set to true
|
652
716
|
|
653
717
|
```ruby
|
654
|
-
Alba.
|
718
|
+
Alba.inflector = :active_support
|
655
719
|
|
656
720
|
class BankAccount
|
657
721
|
attr_reader :account_number
|
@@ -675,7 +739,9 @@ BankAccountResource.new(bank_account).serialize
|
|
675
739
|
# => '{"bank-account":{"account-number":123456789}}'
|
676
740
|
```
|
677
741
|
|
678
|
-
This
|
742
|
+
This is the default behavior from version 2.
|
743
|
+
|
744
|
+
Find more details in the [Inference configuration](#inference-configuration) section.
|
679
745
|
|
680
746
|
#### Key transformation cascading
|
681
747
|
|
@@ -687,7 +753,7 @@ You can also turn it off by setting `cascade: false` option to `transform_keys`.
|
|
687
753
|
|
688
754
|
```ruby
|
689
755
|
class User
|
690
|
-
attr_reader :id, :first_name, :last_name
|
756
|
+
attr_reader :id, :first_name, :last_name, :bank_account
|
691
757
|
|
692
758
|
def initialize(id, first_name, last_name)
|
693
759
|
@id = id
|
@@ -746,7 +812,7 @@ module CustomInflector
|
|
746
812
|
end
|
747
813
|
end
|
748
814
|
|
749
|
-
Alba.
|
815
|
+
Alba.inflector = CustomInflector
|
750
816
|
```
|
751
817
|
|
752
818
|
### Conditional attributes
|
@@ -757,7 +823,7 @@ In these cases, conditional attributes works well. We can pass `if` option to `a
|
|
757
823
|
|
758
824
|
```ruby
|
759
825
|
class User
|
760
|
-
attr_accessor :id, :name, :email
|
826
|
+
attr_accessor :id, :name, :email
|
761
827
|
|
762
828
|
def initialize(id, name, email)
|
763
829
|
@id = id
|
@@ -790,12 +856,12 @@ end
|
|
790
856
|
|
791
857
|
We believe this is clearer than using some (not implemented yet) DSL such as `default` because there are some conditions where default values should be applied (`nil`, `blank?`, `empty?` etc.)
|
792
858
|
|
793
|
-
###
|
859
|
+
### Root key and association resource name inference
|
794
860
|
|
795
|
-
|
861
|
+
If [inference](#inference-configuration) is enabled, Alba tries to infer the root key and association resource names.
|
796
862
|
|
797
863
|
```ruby
|
798
|
-
Alba.
|
864
|
+
Alba.inflector = :active_support
|
799
865
|
|
800
866
|
class User
|
801
867
|
attr_reader :id
|
@@ -825,7 +891,7 @@ end
|
|
825
891
|
class UserResource
|
826
892
|
include Alba::Resource
|
827
893
|
|
828
|
-
|
894
|
+
root_key!
|
829
895
|
|
830
896
|
attributes :id
|
831
897
|
|
@@ -843,6 +909,8 @@ This resource automatically sets its root key to either "users" or "user", depen
|
|
843
909
|
|
844
910
|
Also, you don't have to specify which resource class to use with `many`. Alba infers it from association name.
|
845
911
|
|
912
|
+
Find more details in the [Inference configuration](#inference-configuration) section.
|
913
|
+
|
846
914
|
### Error handling
|
847
915
|
|
848
916
|
You can set error handler globally or per resource using `on_error`.
|
@@ -934,7 +1002,7 @@ class UserResource
|
|
934
1002
|
include Alba::Resource
|
935
1003
|
|
936
1004
|
on_nil do |object, key|
|
937
|
-
if key == age
|
1005
|
+
if key == 'age'
|
938
1006
|
20
|
939
1007
|
else
|
940
1008
|
"User#{object.id}"
|
@@ -990,7 +1058,7 @@ class UserResourceWithoutMeta
|
|
990
1058
|
attributes :id, :name
|
991
1059
|
end
|
992
1060
|
|
993
|
-
|
1061
|
+
UserResourceWithoutMeta.new([user]).serialize(meta: {foo: :bar})
|
994
1062
|
# => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"meta":{"foo":"bar"}}'
|
995
1063
|
```
|
996
1064
|
|
@@ -1014,7 +1082,7 @@ You can validate and convert input with types.
|
|
1014
1082
|
class User
|
1015
1083
|
attr_reader :id, :name, :age, :bio, :admin, :created_at
|
1016
1084
|
|
1017
|
-
def initialize(id, name, age, bio = '', admin = false)
|
1085
|
+
def initialize(id, name, age, bio = '', admin = false)
|
1018
1086
|
@id = id
|
1019
1087
|
@name = name
|
1020
1088
|
@age = age
|
data/alba.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.description = "Alba is the fastest JSON serializer for Ruby. It focuses on performance, flexibility and usability."
|
11
11
|
spec.homepage = 'https://github.com/okuramasafumi/alba'
|
12
12
|
spec.license = 'MIT'
|
13
|
-
spec.required_ruby_version = Gem::Requirement.new('>= 2.
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
|
14
14
|
|
15
15
|
spec.metadata = {
|
16
16
|
'bug_tracker_uri' => 'https://github.com/okuramasafumi/issues',
|
data/benchmark/collection.rb
CHANGED
@@ -1,88 +1,7 @@
|
|
1
1
|
# Benchmark script to run varieties of JSON serializers
|
2
2
|
# Fetch Alba from local, otherwise fetch latest from RubyGems
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
require "bundler/inline"
|
7
|
-
|
8
|
-
gemfile(true) do
|
9
|
-
source "https://rubygems.org"
|
10
|
-
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
11
|
-
|
12
|
-
gem "active_model_serializers"
|
13
|
-
gem "activerecord", "6.1.3"
|
14
|
-
gem "alba", path: '../'
|
15
|
-
gem "benchmark-ips"
|
16
|
-
gem "benchmark-memory"
|
17
|
-
gem "blueprinter"
|
18
|
-
gem "fast_serializer_ruby"
|
19
|
-
gem "jbuilder"
|
20
|
-
gem 'turbostreamer'
|
21
|
-
gem "jserializer"
|
22
|
-
gem "multi_json"
|
23
|
-
gem "panko_serializer"
|
24
|
-
gem "pg"
|
25
|
-
gem "primalize"
|
26
|
-
gem "oj"
|
27
|
-
gem "representable"
|
28
|
-
gem "simple_ams"
|
29
|
-
gem "sqlite3"
|
30
|
-
end
|
31
|
-
|
32
|
-
# --- Test data model setup ---
|
33
|
-
|
34
|
-
require "pg"
|
35
|
-
require "active_record"
|
36
|
-
require "active_record/connection_adapters/postgresql_adapter"
|
37
|
-
require "logger"
|
38
|
-
require "oj"
|
39
|
-
require "sqlite3"
|
40
|
-
Oj.optimize_rails
|
41
|
-
|
42
|
-
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
43
|
-
# ActiveRecord::Base.logger = Logger.new($stdout)
|
44
|
-
|
45
|
-
ActiveRecord::Schema.define do
|
46
|
-
create_table :posts, force: true do |t|
|
47
|
-
t.string :body
|
48
|
-
end
|
49
|
-
|
50
|
-
create_table :comments, force: true do |t|
|
51
|
-
t.integer :post_id
|
52
|
-
t.string :body
|
53
|
-
t.integer :commenter_id
|
54
|
-
end
|
55
|
-
|
56
|
-
create_table :users, force: true do |t|
|
57
|
-
t.string :name
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
class Post < ActiveRecord::Base
|
62
|
-
has_many :comments
|
63
|
-
has_many :commenters, through: :comments, class_name: 'User', source: :commenter
|
64
|
-
|
65
|
-
def attributes
|
66
|
-
{id: nil, body: nil, commenter_names: commenter_names}
|
67
|
-
end
|
68
|
-
|
69
|
-
def commenter_names
|
70
|
-
commenters.pluck(:name)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
class Comment < ActiveRecord::Base
|
75
|
-
belongs_to :post
|
76
|
-
belongs_to :commenter, class_name: 'User'
|
77
|
-
|
78
|
-
def attributes
|
79
|
-
{id: nil, body: nil}
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
class User < ActiveRecord::Base
|
84
|
-
has_many :comments
|
85
|
-
end
|
4
|
+
require_relative 'prep'
|
86
5
|
|
87
6
|
# --- Alba serializers ---
|
88
7
|
|
@@ -160,30 +79,6 @@ class FastSerializerPostResource
|
|
160
79
|
has_many :comments, serializer: FastSerializerCommentResource
|
161
80
|
end
|
162
81
|
|
163
|
-
# --- JBuilder serializers ---
|
164
|
-
|
165
|
-
require "jbuilder"
|
166
|
-
|
167
|
-
class Post
|
168
|
-
def to_builder
|
169
|
-
Jbuilder.new do |post|
|
170
|
-
post.call(self, :id, :body, :commenter_names, :comments)
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
def commenter_names
|
175
|
-
commenters.pluck(:name)
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
class Comment
|
180
|
-
def to_builder
|
181
|
-
Jbuilder.new do |comment|
|
182
|
-
comment.call(self, :id, :body)
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
82
|
# --- Jserializer serializers ---
|
188
83
|
|
189
84
|
require 'jserializer'
|
@@ -220,31 +115,6 @@ class PankoPostSerializer < Panko::Serializer
|
|
220
115
|
end
|
221
116
|
end
|
222
117
|
|
223
|
-
# --- Primalize serializers ---
|
224
|
-
#
|
225
|
-
class PrimalizeCommentResource < Primalize::Single
|
226
|
-
attributes id: integer, body: string
|
227
|
-
end
|
228
|
-
|
229
|
-
class PrimalizePostResource < Primalize::Single
|
230
|
-
alias post object
|
231
|
-
|
232
|
-
attributes(
|
233
|
-
id: integer,
|
234
|
-
body: string,
|
235
|
-
comments: array(primalize(PrimalizeCommentResource)),
|
236
|
-
commenter_names: array(string),
|
237
|
-
)
|
238
|
-
|
239
|
-
def commenter_names
|
240
|
-
post.commenters.pluck(:name)
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
class PrimalizePostsResource < Primalize::Many
|
245
|
-
attributes posts: enumerable(PrimalizePostResource)
|
246
|
-
end
|
247
|
-
|
248
118
|
# --- Representable serializers ---
|
249
119
|
|
250
120
|
require "representable"
|
@@ -349,13 +219,6 @@ end
|
|
349
219
|
ams = Proc.new { ActiveModelSerializers::SerializableResource.new(posts, {each_serializer: AMSPostSerializer}).to_json }
|
350
220
|
blueprinter = Proc.new { PostBlueprint.render(posts) }
|
351
221
|
fast_serializer = Proc.new { FastSerializerPostResource.new(posts).to_json }
|
352
|
-
jbuilder = Proc.new do
|
353
|
-
Jbuilder.new do |json|
|
354
|
-
json.array!(posts) do |post|
|
355
|
-
json.post post.to_builder
|
356
|
-
end
|
357
|
-
end.target!
|
358
|
-
end
|
359
222
|
jserializer = Proc.new { JserializerPostSerializer.new(posts, is_collection: true).to_json }
|
360
223
|
panko = proc { Panko::ArraySerializer.new(posts, each_serializer: PankoPostSerializer).to_json }
|
361
224
|
primalize = proc { PrimalizePostsResource.new(posts: posts).to_json }
|
@@ -368,22 +231,25 @@ turbostreamer = Proc.new { TurbostreamerSerializer.new(posts).to_json }
|
|
368
231
|
|
369
232
|
# --- Execute the serializers to check their output ---
|
370
233
|
GC.disable
|
371
|
-
puts "
|
234
|
+
puts "Checking outputs..."
|
235
|
+
correct = alba.call
|
236
|
+
parsed_correct = JSON.parse(correct)
|
372
237
|
{
|
373
|
-
alba: alba,
|
374
238
|
alba_inline: alba_inline,
|
375
239
|
ams: ams,
|
376
240
|
blueprinter: blueprinter,
|
377
241
|
fast_serializer: fast_serializer,
|
378
|
-
jbuilder: jbuilder, # different order
|
379
242
|
jserializer: jserializer,
|
380
243
|
panko: panko,
|
381
|
-
primalize: primalize,
|
382
244
|
rails: rails,
|
383
245
|
representable: representable,
|
384
246
|
simple_ams: simple_ams,
|
385
247
|
turbostreamer: turbostreamer
|
386
|
-
}.each
|
248
|
+
}.each do |name, serializer|
|
249
|
+
result = serializer.call
|
250
|
+
parsed_result = JSON.parse(result)
|
251
|
+
puts "#{name} yields wrong output: #{parsed_result}" unless parsed_result == parsed_correct
|
252
|
+
end
|
387
253
|
|
388
254
|
# --- Run the benchmarks ---
|
389
255
|
|
@@ -394,10 +260,8 @@ Benchmark.ips do |x|
|
|
394
260
|
x.report(:ams, &ams)
|
395
261
|
x.report(:blueprinter, &blueprinter)
|
396
262
|
x.report(:fast_serializer, &fast_serializer)
|
397
|
-
x.report(:jbuilder, &jbuilder)
|
398
263
|
x.report(:jserializer, &jserializer)
|
399
264
|
x.report(:panko, &panko)
|
400
|
-
x.report(:primalize, &primalize)
|
401
265
|
x.report(:rails, &rails)
|
402
266
|
x.report(:representable, &representable)
|
403
267
|
x.report(:simple_ams, &simple_ams)
|
@@ -414,10 +278,8 @@ Benchmark.memory do |x|
|
|
414
278
|
x.report(:ams, &ams)
|
415
279
|
x.report(:blueprinter, &blueprinter)
|
416
280
|
x.report(:fast_serializer, &fast_serializer)
|
417
|
-
x.report(:jbuilder, &jbuilder)
|
418
281
|
x.report(:jserializer, &jserializer)
|
419
282
|
x.report(:panko, &panko)
|
420
|
-
x.report(:primalize, &primalize)
|
421
283
|
x.report(:rails, &rails)
|
422
284
|
x.report(:representable, &representable)
|
423
285
|
x.report(:simple_ams, &simple_ams)
|