alba 2.1.0 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +1 -0
- data/.github/workflows/main.yml +1 -1
- data/.rubocop.yml +33 -2
- data/CHANGELOG.md +24 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +4 -2
- data/HACKING.md +2 -1
- data/README.md +94 -29
- data/alba.gemspec +1 -1
- data/benchmark/README.md +52 -48
- data/docs/rails.md +3 -1
- data/lib/alba/association.rb +1 -1
- data/lib/alba/conditional_attribute.rb +1 -1
- data/lib/alba/layout.rb +2 -2
- data/lib/alba/nested_attribute.rb +5 -3
- data/lib/alba/railtie.rb +8 -0
- data/lib/alba/resource.rb +39 -39
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +46 -6
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3ba9736fa95cae41e16375e21770a7244b1ee5af9787090a1dadbe3f50839221
|
4
|
+
data.tar.gz: cb06fc313a5abb4914fceb3eb044911bd556d11cfa2239b3509147f5f992ab9e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4814af5d6e0a69d34ba6f3c9ef9292e7a83196ff3d231851d799857518888e9db4bc075a0cbc88ae8e7e39aa20e724085224e1944b843edc7b685ef372cefa71
|
7
|
+
data.tar.gz: 9cb559556d0e44ea4218328d645f4666e0dd5cf63a4b380b64e7e6bf1460724ecd3e04b8eb138ac1aac30176a5d0491db1451e3bdaf2130771ab06bdbb2e4d50
|
data/.codeclimate.yml
CHANGED
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
@@ -3,7 +3,12 @@
|
|
3
3
|
inherit_gem:
|
4
4
|
rubocop-sensible: 'config/rubocop.yml'
|
5
5
|
|
6
|
+
inherit_mode:
|
7
|
+
merge:
|
8
|
+
- Exclude
|
9
|
+
|
6
10
|
require:
|
11
|
+
- rubocop-md
|
7
12
|
- rubocop-minitest
|
8
13
|
- rubocop-performance
|
9
14
|
- rubocop-rake
|
@@ -16,12 +21,17 @@ AllCops:
|
|
16
21
|
- 'script/**/*.rb'
|
17
22
|
NewCops: enable
|
18
23
|
EnabledByDefault: true
|
19
|
-
TargetRubyVersion: 2.
|
24
|
+
TargetRubyVersion: 2.7
|
20
25
|
|
21
26
|
# Items in Gemfile is dev dependencies and we don't have to specify versions.
|
22
27
|
Bundler/GemVersion:
|
23
28
|
Enabled: false
|
24
29
|
|
30
|
+
# Test class is a class, but not really
|
31
|
+
Layout/ClassStructure:
|
32
|
+
Exclude:
|
33
|
+
- 'test/**/*'
|
34
|
+
|
25
35
|
# We'd like to write something like:
|
26
36
|
# assert_equal(
|
27
37
|
# expected,
|
@@ -58,6 +68,11 @@ Metrics/ParameterLists:
|
|
58
68
|
Exclude:
|
59
69
|
- 'test/**/*.rb'
|
60
70
|
|
71
|
+
# Putting extra empty line is not valuable in test
|
72
|
+
# We prefer shorter test code
|
73
|
+
Minitest/EmptyLineBeforeAssertionMethods:
|
74
|
+
Enabled: false
|
75
|
+
|
61
76
|
# We need to eval resource code to test errors on resource classes
|
62
77
|
Security/Eval:
|
63
78
|
Exclude:
|
@@ -75,14 +90,26 @@ Style/Copyright:
|
|
75
90
|
Style/DisableCopsWithinSourceCodeDirective:
|
76
91
|
Enabled: false
|
77
92
|
|
93
|
+
Style/Documentation:
|
94
|
+
Exclude:
|
95
|
+
- 'test/**/*'
|
96
|
+
|
97
|
+
Style/DocumentationMethod:
|
98
|
+
Exclude:
|
99
|
+
- 'README.md'
|
100
|
+
|
78
101
|
Style/FrozenStringLiteralComment:
|
79
102
|
Enabled: false
|
80
103
|
|
104
|
+
Style/ImplicitRuntimeError:
|
105
|
+
Exclude:
|
106
|
+
- 'README.md'
|
107
|
+
|
81
108
|
Style/InlineComment:
|
82
109
|
Enabled: false
|
83
110
|
|
84
111
|
Style/MethodCallWithArgsParentheses:
|
85
|
-
|
112
|
+
AllowedMethods: ['require', 'require_relative', 'include', 'extend', 'puts', 'p', 'warn', 'raise', 'send', 'public_send']
|
86
113
|
Exclude:
|
87
114
|
# There are so many `attributes` call without parenthese and that's absolutely fine
|
88
115
|
- 'test/**/*.rb'
|
@@ -90,3 +117,7 @@ Style/MethodCallWithArgsParentheses:
|
|
90
117
|
# There are so many cases we just want `if` expression!
|
91
118
|
Style/MissingElse:
|
92
119
|
EnforcedStyle: case
|
120
|
+
|
121
|
+
Style/OptionalBooleanParameter:
|
122
|
+
Exclude:
|
123
|
+
- 'README.md'
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [2.3.0] 2023-04-24
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Add compatibility option for key [#304](https://github.com/okuramasafumi/alba/pull/304)
|
14
|
+
- It now infers resource name from Serializer [#309](https://github.com/okuramasafumi/alba/pull/309)
|
15
|
+
- `Alba.serialize` is easier to use for multiple root keys [#311](https://github.com/okuramasafumi/alba/pull/311)
|
16
|
+
- Gives access to params in nested_attribute [#312](https://github.com/okuramasafumi/alba/pull/312)
|
17
|
+
- Thank you, @GabrielErbetta
|
18
|
+
|
19
|
+
## [2.2.0] 2023-02-17
|
20
|
+
|
21
|
+
### Added
|
22
|
+
|
23
|
+
- Rails integration to set default inflector [#298](https://github.com/okuramasafumi/alba/pull/298)
|
24
|
+
|
25
|
+
### Fixed
|
26
|
+
|
27
|
+
- Fix cascade not working with association and inheritance [#300](https://github.com/okuramasafumi/alba/pull/300)
|
28
|
+
|
29
|
+
### Removed
|
30
|
+
|
31
|
+
- Drop support of Ruby 2.6
|
32
|
+
|
9
33
|
## [2.1.0] 2022-12-03
|
10
34
|
|
11
35
|
### Added
|
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
@@ -7,10 +7,12 @@ gem 'activesupport', require: false # For backend
|
|
7
7
|
gem 'dry-inflector', require: false # For inflection
|
8
8
|
gem 'ffaker', require: false # For testing
|
9
9
|
gem 'minitest', '~> 5.14' # For test
|
10
|
+
gem 'railties', require: false # For Rails integration testing
|
10
11
|
gem 'rake', '~> 13.0' # For test and automation
|
11
12
|
gem 'rubocop', '>= 0.79.0', require: false # For lint
|
12
|
-
gem 'rubocop-
|
13
|
-
gem 'rubocop-
|
13
|
+
gem 'rubocop-md', '~> 1.0', require: false # For lint
|
14
|
+
gem 'rubocop-minitest', '>= 0.25.0', require: false # For lint
|
15
|
+
gem 'rubocop-performance', '>= 1.15.0', require: false # For lint
|
14
16
|
gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
15
17
|
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
16
18
|
gem 'ruby-lsp', require: false # For language server
|
data/HACKING.md
CHANGED
@@ -17,6 +17,7 @@ Class methods (DSL):
|
|
17
17
|
* `attribute` for block style attribute
|
18
18
|
* `attributes` for symbol style attribute
|
19
19
|
* `association` and its aliases such as `one` for association
|
20
|
+
* `nested` for nested attribute
|
20
21
|
|
21
22
|
Instance methods:
|
22
23
|
|
@@ -29,7 +30,7 @@ Other methods are rather trivial. They'll be added to this list when it turned o
|
|
29
30
|
|
30
31
|
In `Alba::Resource` module there are some things to note.
|
31
32
|
|
32
|
-
`@object` is an object for serialization. It's either singular object or collection.
|
33
|
+
`@object` is an object for serialization. It's either a singular object or a collection.
|
33
34
|
|
34
35
|
Attribute object can be either `Symbol`, `Proc`, `Alba::Association` or `Alba::TypedAttribute`.
|
35
36
|
|
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!
|
@@ -33,7 +43,7 @@ Alba is faster than most of the alternatives. We have a [benchmark](https://gith
|
|
33
43
|
|
34
44
|
### Easy
|
35
45
|
|
36
|
-
Alba is easy to use because there are only a few methods to remember. It's also easy to understand due to clean and
|
46
|
+
Alba is easy to use because there are only a few methods to remember. It's also easy to understand due to clean and small codebase. Finally it's easy to extend since it provides some methods for override to change default behavior of Alba.
|
37
47
|
|
38
48
|
### Feature rich
|
39
49
|
|
@@ -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
|
|
@@ -133,13 +143,27 @@ Alba.inflector = nil
|
|
133
143
|
To check if inference is enabled etc, inspect the return value of `inflector`:
|
134
144
|
|
135
145
|
```ruby
|
136
|
-
if Alba.inflector
|
137
|
-
puts
|
146
|
+
if Alba.inflector.nil?
|
147
|
+
puts 'inflector not set'
|
138
148
|
else
|
139
149
|
puts "inflector is set to #{Alba.inflector}"
|
140
150
|
end
|
141
151
|
```
|
142
152
|
|
153
|
+
### Naming
|
154
|
+
|
155
|
+
Alba tries to infer resource name from class name like the following.
|
156
|
+
|
157
|
+
|Class name|Resource name|
|
158
|
+
| --- | --- |
|
159
|
+
| FooResource | Foo |
|
160
|
+
| FooSerializer | Foo |
|
161
|
+
| FooElse | FooElse |
|
162
|
+
|
163
|
+
Resource name is used as the default name of the root key, so you might want to name it ending with "Resource" or "Serializer"
|
164
|
+
|
165
|
+
When you use Alba with Rails, it's recommended to put your resource/serializer classes in corresponding directory such as `app/resources` or `app/serializers`.
|
166
|
+
|
143
167
|
### Simple serialization with root key
|
144
168
|
|
145
169
|
You can define attributes with (yes) `attributes` macro with attribute names. If your attribute need some calculations, you can use `attribute` with block.
|
@@ -147,6 +171,7 @@ You can define attributes with (yes) `attributes` macro with attribute names. If
|
|
147
171
|
```ruby
|
148
172
|
class User
|
149
173
|
attr_accessor :id, :name, :email, :created_at, :updated_at
|
174
|
+
|
150
175
|
def initialize(id, name, email)
|
151
176
|
@id = id
|
152
177
|
@name = name
|
@@ -346,11 +371,11 @@ class UserResource
|
|
346
371
|
|
347
372
|
# Second proc works as a filter
|
348
373
|
many :articles,
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
374
|
+
proc { |articles, params, user|
|
375
|
+
filter = params[:filter] || :odd?
|
376
|
+
articles.select { |a| a.id.__send__(filter) && !user.banned }
|
377
|
+
},
|
378
|
+
resource: ArticleResource
|
354
379
|
end
|
355
380
|
|
356
381
|
user = User.new(1)
|
@@ -374,8 +399,8 @@ class UserResource
|
|
374
399
|
attributes :id
|
375
400
|
|
376
401
|
many :articles,
|
377
|
-
|
378
|
-
|
402
|
+
key: 'my_articles', # Set key here
|
403
|
+
resource: ArticleResource
|
379
404
|
end
|
380
405
|
UserResource.new(user).serialize
|
381
406
|
# => '{"id":1,"my_articles":[{"title":"Hello World!"}]}'
|
@@ -442,7 +467,7 @@ class FooResourceWithParamsOverride
|
|
442
467
|
|
443
468
|
root_key :foo
|
444
469
|
|
445
|
-
one :bar, resource: BarResource, params: {
|
470
|
+
one :bar, resource: BarResource, params: {expose_secret: false}
|
446
471
|
end
|
447
472
|
|
448
473
|
Baz = Struct.new(:data, :secret)
|
@@ -534,6 +559,48 @@ Alba.serialize(something)
|
|
534
559
|
|
535
560
|
Although this might be useful sometimes, it's generally recommended to define a class for Resource.
|
536
561
|
|
562
|
+
#### Inline definition for multiple root keys
|
563
|
+
|
564
|
+
While Alba doesn't directly support multiple root keys, you can simulate it with `Alba.serialize`.
|
565
|
+
|
566
|
+
```ruby
|
567
|
+
# Define foo and bar local variables here
|
568
|
+
|
569
|
+
Alba.serialize do
|
570
|
+
attribute :key1 do
|
571
|
+
FooResource.new(foo).to_h
|
572
|
+
end
|
573
|
+
|
574
|
+
attribute :key2 do
|
575
|
+
BarResource.new(bar).to_h
|
576
|
+
end
|
577
|
+
end
|
578
|
+
# => JSON containing "key1" and "key2" as root keys
|
579
|
+
```
|
580
|
+
|
581
|
+
Note that we must use `to_h`, not `serialize`, with resources.
|
582
|
+
|
583
|
+
We can also generate a JSON with multiple root keys without making any class by the combination of `Alba.serialize` and `Alba.hashify`.
|
584
|
+
|
585
|
+
```ruby
|
586
|
+
# Define foo and bar local variables here
|
587
|
+
|
588
|
+
Alba.serialize do
|
589
|
+
attribute :foo do
|
590
|
+
Alba.hashify(foo) do
|
591
|
+
attributes :id, :name # For example
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
attribute :bar do
|
596
|
+
Alba.hashify(bar) do
|
597
|
+
attributes :id
|
598
|
+
end
|
599
|
+
end
|
600
|
+
end
|
601
|
+
# => JSON containing "foo" and "bar" as root keys
|
602
|
+
```
|
603
|
+
|
537
604
|
### Serializable Hash
|
538
605
|
|
539
606
|
Instead of serializing to JSON, you can also output a Hash by calling `serializable_hash` or the `to_h` alias. Note also that the `serialize` method is aliased as `to_json`.
|
@@ -786,20 +853,15 @@ A custom inflector can be plugged in as follows.
|
|
786
853
|
module CustomInflector
|
787
854
|
module_function
|
788
855
|
|
789
|
-
def camelize(string)
|
790
|
-
end
|
856
|
+
def camelize(string); end
|
791
857
|
|
792
|
-
def camelize_lower(string)
|
793
|
-
end
|
858
|
+
def camelize_lower(string); end
|
794
859
|
|
795
|
-
def dasherize(string)
|
796
|
-
end
|
860
|
+
def dasherize(string); end
|
797
861
|
|
798
|
-
def underscore(string)
|
799
|
-
end
|
862
|
+
def underscore(string); end
|
800
863
|
|
801
|
-
def classify(string)
|
802
|
-
end
|
864
|
+
def classify(string); end
|
803
865
|
end
|
804
866
|
|
805
867
|
Alba.inflector = CustomInflector
|
@@ -916,7 +978,7 @@ class User
|
|
916
978
|
end
|
917
979
|
|
918
980
|
def email
|
919
|
-
raise
|
981
|
+
raise 'Error!'
|
920
982
|
end
|
921
983
|
end
|
922
984
|
|
@@ -1072,7 +1134,7 @@ You can validate and convert input with types.
|
|
1072
1134
|
class User
|
1073
1135
|
attr_reader :id, :name, :age, :bio, :admin, :created_at
|
1074
1136
|
|
1075
|
-
def initialize(id, name, age, bio = '', admin = false)
|
1137
|
+
def initialize(id, name, age, bio = '', admin = false)
|
1076
1138
|
@id = id
|
1077
1139
|
@name = name
|
1078
1140
|
@age = age
|
@@ -1112,8 +1174,10 @@ Sometimes we want to serialize a collection into a Hash, not an Array. It's poss
|
|
1112
1174
|
```ruby
|
1113
1175
|
class User
|
1114
1176
|
attr_reader :id, :name
|
1177
|
+
|
1115
1178
|
def initialize(id, name)
|
1116
|
-
@id
|
1179
|
+
@id = id
|
1180
|
+
@name = name
|
1117
1181
|
end
|
1118
1182
|
end
|
1119
1183
|
|
@@ -1279,8 +1343,8 @@ module AlbaExtension
|
|
1279
1343
|
# Here attrs are an Array of Symbol
|
1280
1344
|
def formatted_time_attributes(*attrs)
|
1281
1345
|
attrs.each do |attr|
|
1282
|
-
attribute
|
1283
|
-
time = object.
|
1346
|
+
attribute(attr) do |object|
|
1347
|
+
time = object.__send__(attr)
|
1284
1348
|
time.strftime('%m/%d/%Y')
|
1285
1349
|
end
|
1286
1350
|
end
|
@@ -1337,13 +1401,14 @@ Alba currently doesn't support logging directly, but you can add your own loggin
|
|
1337
1401
|
|
1338
1402
|
```ruby
|
1339
1403
|
module Logging
|
1340
|
-
|
1404
|
+
# `...` was added in Ruby 2.7
|
1405
|
+
def serialize(...)
|
1341
1406
|
puts serializable_hash
|
1342
1407
|
super(...)
|
1343
1408
|
end
|
1344
1409
|
end
|
1345
1410
|
|
1346
|
-
FooResource.prepend
|
1411
|
+
FooResource.prepend(Logging)
|
1347
1412
|
FooResource.new(foo).serialize
|
1348
1413
|
# => "{:id=>1}" is printed
|
1349
1414
|
```
|
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/README.md
CHANGED
@@ -10,72 +10,76 @@ Machine spec:
|
|
10
10
|
|
11
11
|
|Key|Value|
|
12
12
|
|---|---|
|
13
|
-
|OS|macOS
|
13
|
+
|OS|macOS 13.2.1|
|
14
14
|
|CPU|Intel Corei7 Quad Core 2.3Ghz|
|
15
15
|
|RAM|32GB|
|
16
|
-
|Ruby|ruby 3.
|
16
|
+
|Ruby|ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-darwin21]|
|
17
|
+
|
18
|
+
Library versions:
|
19
|
+
|
20
|
+
|Library|Version|
|
21
|
+
|---|---|
|
22
|
+
|alba|2.2.0|
|
23
|
+
|blueprinter|0.25.3|
|
24
|
+
|fast_serializer_ruby|0.6.9|
|
25
|
+
|jserializer|0.2.1|
|
26
|
+
|oj|3.14.2|
|
27
|
+
|simple_ams|0.2.6|
|
28
|
+
|representable|3.2.0|
|
29
|
+
|turbostreamer|1.10.0|
|
30
|
+
|jbuilder|2.11.5|
|
31
|
+
|panko_serializer|0.7.9|
|
32
|
+
|active_model_serializers|0.10.13|
|
17
33
|
|
18
34
|
`benchmark-ips` with `Oj.optimize_rails`:
|
19
35
|
|
20
36
|
```
|
21
37
|
Comparison:
|
22
|
-
panko:
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
blueprinter: 54.1 i/s - 4.95x (± 0.00) slower
|
34
|
-
representable: 35.9 i/s - 7.46x (± 0.00) slower
|
35
|
-
simple_ams: 25.4 i/s - 10.53x (± 0.00) slower
|
36
|
-
ams: 9.1 i/s - 29.39x (± 0.00) slower
|
38
|
+
panko: 310.4 i/s
|
39
|
+
jserializer: 120.6 i/s - 2.57x slower
|
40
|
+
turbostreamer: 117.3 i/s - 2.65x slower
|
41
|
+
rails: 114.0 i/s - 2.72x slower
|
42
|
+
alba_inline: 99.3 i/s - 3.13x slower
|
43
|
+
alba: 94.1 i/s - 3.30x slower
|
44
|
+
fast_serializer: 67.8 i/s - 4.58x slower
|
45
|
+
blueprinter: 57.6 i/s - 5.39x slower
|
46
|
+
representable: 36.3 i/s - 8.56x slower
|
47
|
+
simple_ams: 23.3 i/s - 13.32x slower
|
48
|
+
ams: 10.9 i/s - 28.53x slower
|
37
49
|
```
|
38
50
|
|
39
51
|
`benchmark-ips` without `Oj.optimize_rails`:
|
40
52
|
|
41
53
|
```
|
42
54
|
Comparison:
|
43
|
-
panko:
|
44
|
-
turbostreamer:
|
45
|
-
|
46
|
-
alba_inline:
|
47
|
-
|
48
|
-
fast_serializer:
|
49
|
-
blueprinter:
|
50
|
-
rails:
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
representable: 27.8 i/s - 10.23x (± 0.00) slower
|
55
|
-
jsonapi: 27.5 i/s - 10.34x (± 0.00) slower
|
56
|
-
simple_ams: 16.9 i/s - 16.75x (± 0.00) slower
|
57
|
-
ams: 8.3 i/s - 34.36x (± 0.00) slower
|
55
|
+
panko: 326.1 i/s
|
56
|
+
turbostreamer: 120.6 i/s - 2.70x slower
|
57
|
+
jserializer: 119.2 i/s - 2.74x slower
|
58
|
+
alba_inline: 104.3 i/s - 3.13x slower
|
59
|
+
alba: 102.2 i/s - 3.19x slower
|
60
|
+
fast_serializer: 66.9 i/s - 4.88x slower
|
61
|
+
blueprinter: 56.7 i/s - 5.75x slower
|
62
|
+
rails: 33.9 i/s - 9.63x slower
|
63
|
+
representable: 30.3 i/s - 10.77x slower
|
64
|
+
simple_ams: 16.4 i/s - 19.84x slower
|
65
|
+
ams: 9.4 i/s - 34.56x slower
|
58
66
|
```
|
59
67
|
|
60
68
|
`benchmark-memory`:
|
61
69
|
|
62
70
|
```
|
63
71
|
Comparison:
|
64
|
-
panko:
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
jsonapi: 2279958 allocated - 9.89x more
|
76
|
-
representable: 2869166 allocated - 12.45x more
|
77
|
-
ams: 4473161 allocated - 19.41x more
|
78
|
-
simple_ams: 7868345 allocated - 34.15x more
|
72
|
+
panko: 242426 allocated
|
73
|
+
turbostreamer: 817568 allocated - 3.37x more
|
74
|
+
jserializer: 831705 allocated - 3.43x more
|
75
|
+
alba: 1072217 allocated - 4.42x more
|
76
|
+
alba_inline: 1084889 allocated - 4.48x more
|
77
|
+
fast_serializer: 1244385 allocated - 5.13x more
|
78
|
+
rails: 1272761 allocated - 5.25x more
|
79
|
+
blueprinter: 1680137 allocated - 6.93x more
|
80
|
+
representable: 2892425 allocated - 11.93x more
|
81
|
+
ams: 4479569 allocated - 18.48x more
|
82
|
+
simple_ams: 6957913 allocated - 28.70x more
|
79
83
|
```
|
80
84
|
|
81
|
-
Conclusion: panko is extremely fast but it's a C extension gem. As pure Ruby gems, Alba, turbostreamer and jserializer are notably faster than others. With `Oj.optimize_rails`
|
85
|
+
Conclusion: panko is extremely fast but it's a C extension gem. As pure Ruby gems, Alba, `turbostreamer` and `jserializer` are notably faster than others, but Alba is slightly slower than other two. With `Oj.optimize_rails`, `jbuilder` and Rails standard serialization are also fast.
|
data/docs/rails.md
CHANGED
@@ -14,11 +14,13 @@ You might want to add some configurations to initializer file such as `alba.rb`
|
|
14
14
|
```ruby
|
15
15
|
# alba.rb
|
16
16
|
Alba.backend = :active_support
|
17
|
-
Alba.
|
17
|
+
Alba.inflector = :active_support
|
18
18
|
```
|
19
19
|
|
20
20
|
You can also use `:oj_rails` for backend if you prefer using Oj.
|
21
21
|
|
22
|
+
Alba 2.2 introduced new Rails integration so that you don't have to add initializer file for setting inflector. You still need to add initializer file if you want to set backend or configure inflector with something different from `active_support`.
|
23
|
+
|
22
24
|
## Rendering JSON
|
23
25
|
|
24
26
|
You can render JSON with Rails in two ways. One way is to pass JSON String.
|
data/lib/alba/association.rb
CHANGED
@@ -32,7 +32,7 @@ module Alba
|
|
32
32
|
# @param params [Hash] user-given Hash for arbitrary data
|
33
33
|
# @return [Hash]
|
34
34
|
def to_h(target, within: nil, params: {})
|
35
|
-
params = params.merge(@params)
|
35
|
+
params = params.merge(@params)
|
36
36
|
@object = target.__send__(@name)
|
37
37
|
@object = @condition.call(object, params, target) if @condition
|
38
38
|
return if @object.nil?
|
@@ -20,7 +20,7 @@ module Alba
|
|
20
20
|
return Alba::REMOVE_KEY unless condition_passes?(resource, object)
|
21
21
|
|
22
22
|
fetched_attribute = yield(@body)
|
23
|
-
return fetched_attribute
|
23
|
+
return fetched_attribute unless with_two_arity_proc_condition
|
24
24
|
|
25
25
|
return Alba::REMOVE_KEY unless resource.instance_exec(object, attribute_from_association_body_or(fetched_attribute), &@condition)
|
26
26
|
|
data/lib/alba/layout.rb
CHANGED
@@ -8,8 +8,8 @@ module Alba
|
|
8
8
|
|
9
9
|
def_delegators :@resource, :object, :params, :serializable_hash, :to_h
|
10
10
|
|
11
|
-
# @
|
12
|
-
# @
|
11
|
+
# @param file [String] name of the layout file
|
12
|
+
# @param inline [Proc] a proc returning JSON string or a Hash representing JSON
|
13
13
|
def initialize(file:, inline:)
|
14
14
|
if file
|
15
15
|
raise ArgumentError, 'File layout must be a String representing filename' unless file.is_a?(String)
|
@@ -8,12 +8,14 @@ module Alba
|
|
8
8
|
@block = block
|
9
9
|
end
|
10
10
|
|
11
|
-
# @
|
12
|
-
|
11
|
+
# @param object [Object] the object being serialized
|
12
|
+
# @param params [Hash] params Hash inherited from Resource
|
13
|
+
# @return [Hash] hash serialized from running the class body in the object
|
14
|
+
def value(object:, params:)
|
13
15
|
resource_class = Alba.resource_class
|
14
16
|
resource_class.transform_keys(@key_transformation)
|
15
17
|
resource_class.class_eval(&@block)
|
16
|
-
resource_class.new(object).serializable_hash
|
18
|
+
resource_class.new(object, params: params).serializable_hash
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
data/lib/alba/railtie.rb
ADDED
data/lib/alba/resource.rb
CHANGED
@@ -11,21 +11,27 @@ module Alba
|
|
11
11
|
module Resource
|
12
12
|
# @!parse include InstanceMethods
|
13
13
|
# @!parse extend ClassMethods
|
14
|
-
DSLS = {_attributes: {}, _key: nil, _key_for_collection: nil, _meta: nil, _transform_type: :none, _transforming_root_key: false, _on_error: nil, _on_nil: nil, _layout: nil, _collection_key: nil}.freeze # rubocop:disable Layout/LineLength
|
14
|
+
DSLS = {_attributes: {}, _key: nil, _key_for_collection: nil, _meta: nil, _transform_type: :none, _transforming_root_key: false, _key_transformation_cascade: true, _on_error: nil, _on_nil: nil, _layout: nil, _collection_key: nil}.freeze # rubocop:disable Layout/LineLength
|
15
15
|
private_constant :DSLS
|
16
16
|
|
17
17
|
WITHIN_DEFAULT = Object.new.freeze
|
18
18
|
private_constant :WITHIN_DEFAULT
|
19
19
|
|
20
|
+
# `setup` method is meta-programmatically defined here for performance.
|
20
21
|
# @private
|
21
|
-
def self.included(base)
|
22
|
+
def self.included(base) # rubocop:disable Metrics/MethodLength
|
22
23
|
super
|
24
|
+
setup_method_body = 'private def _setup;'
|
23
25
|
base.class_eval do
|
24
26
|
# Initialize
|
25
27
|
DSLS.each do |name, initial|
|
26
28
|
instance_variable_set("@#{name}", initial.dup) unless instance_variable_defined?("@#{name}")
|
29
|
+
setup_method_body << "@#{name} = self.class.#{name};"
|
27
30
|
end
|
31
|
+
base.define_method(:encode, Alba.encoder)
|
28
32
|
end
|
33
|
+
setup_method_body << 'end'
|
34
|
+
base.class_eval(setup_method_body, __FILE__, __LINE__ + 1)
|
29
35
|
base.include InstanceMethods
|
30
36
|
base.extend ClassMethods
|
31
37
|
end
|
@@ -41,7 +47,7 @@ module Alba
|
|
41
47
|
@object = object
|
42
48
|
@params = params
|
43
49
|
@within = within
|
44
|
-
|
50
|
+
_setup
|
45
51
|
end
|
46
52
|
|
47
53
|
# Serialize object into JSON string
|
@@ -78,10 +84,10 @@ module Alba
|
|
78
84
|
#
|
79
85
|
# @param root_key [Symbol, nil, true]
|
80
86
|
# @param meta [Hash] metadata for this seialization
|
81
|
-
# @param symbolize_root_key [Boolean] determines if root key should be symbolized
|
82
87
|
# @return [Hash]
|
83
88
|
def as_json(root_key: nil, meta: {})
|
84
|
-
key = root_key.nil? ? fetch_key : root_key
|
89
|
+
key = root_key.nil? ? fetch_key : root_key
|
90
|
+
key = Alba.regularize_key(key)
|
85
91
|
if key && !key.empty?
|
86
92
|
h = {key => serializable_hash}
|
87
93
|
hash_with_metadata(h, meta)
|
@@ -100,15 +106,9 @@ module Alba
|
|
100
106
|
|
101
107
|
private
|
102
108
|
|
103
|
-
def encode(hash)
|
104
|
-
Alba.encoder.call(hash)
|
105
|
-
end
|
106
|
-
|
107
109
|
def _to_json(root_key, meta, options)
|
108
110
|
options.reject! { |k, _| %i[layout prefixes template status].include?(k) } # Rails specific guard
|
109
|
-
|
110
|
-
names = options.map { |k, v| k unless v.nil? }
|
111
|
-
names.compact!
|
111
|
+
names = options.filter_map { |k, v| k unless v.nil? }
|
112
112
|
unless names.empty?
|
113
113
|
names.sort!
|
114
114
|
names.map! { |s| "\"#{s}\"" }
|
@@ -135,7 +135,11 @@ module Alba
|
|
135
135
|
|
136
136
|
def serializable_hash_for_collection
|
137
137
|
if @_collection_key
|
138
|
-
@object.to_h
|
138
|
+
@object.to_h do |item|
|
139
|
+
k = item.public_send(@_collection_key)
|
140
|
+
key = Alba.regularize_key(k)
|
141
|
+
[key, converter.call(item)]
|
142
|
+
end
|
139
143
|
else
|
140
144
|
@object.each_with_object([], &collection_converter)
|
141
145
|
end
|
@@ -148,34 +152,26 @@ module Alba
|
|
148
152
|
end
|
149
153
|
|
150
154
|
def _key_for_collection
|
151
|
-
|
152
|
-
|
153
|
-
else
|
154
|
-
@_key_for_collection == true ? raise_root_key_inference_error : @_key_for_collection.to_s
|
155
|
-
end
|
155
|
+
k = @_key_for_collection == true ? resource_name(pluralized: true) : @_key_for_collection
|
156
|
+
Alba.regularize_key(k)
|
156
157
|
end
|
157
158
|
|
158
|
-
# @return [String]
|
159
159
|
def _key
|
160
|
-
|
161
|
-
|
162
|
-
else
|
163
|
-
@_key == true ? raise_root_key_inference_error : @_key.to_s
|
164
|
-
end
|
160
|
+
k = @_key == true ? resource_name(pluralized: false) : @_key
|
161
|
+
Alba.regularize_key(k)
|
165
162
|
end
|
166
163
|
|
167
164
|
def resource_name(pluralized: false)
|
168
|
-
class_name = self.class.name
|
169
165
|
inflector = Alba.inflector
|
170
|
-
|
166
|
+
raise Alba::Error, 'You must set inflector when setting root key as true.' unless inflector
|
167
|
+
|
168
|
+
class_name = self.class.name
|
169
|
+
suffix = class_name.end_with?('Resource') ? 'Resource' : 'Serializer'
|
170
|
+
name = inflector.demodulize(class_name).delete_suffix(suffix)
|
171
171
|
underscore_name = inflector.underscore(name)
|
172
172
|
pluralized ? inflector.pluralize(underscore_name) : underscore_name
|
173
173
|
end
|
174
174
|
|
175
|
-
def raise_root_key_inference_error
|
176
|
-
raise Alba::Error, 'You must set inflector when setting root key as true.'
|
177
|
-
end
|
178
|
-
|
179
175
|
def transforming_root_key?
|
180
176
|
@_transforming_root_key
|
181
177
|
end
|
@@ -238,14 +234,16 @@ module Alba
|
|
238
234
|
end
|
239
235
|
end
|
240
236
|
|
241
|
-
|
242
|
-
|
243
|
-
key = key.to_s
|
244
|
-
return key if @_transform_type == :none || key.empty? # We can skip transformation
|
237
|
+
def transform_key(key)
|
238
|
+
return Alba.regularize_key(key) if @_transform_type == :none || key.nil? || key.empty? # We can skip transformation
|
245
239
|
|
246
240
|
inflector = Alba.inflector
|
247
241
|
raise Alba::Error, 'Inflector is nil. You must set inflector before transforming keys.' unless inflector
|
248
242
|
|
243
|
+
Alba.regularize_key(_transform_key(inflector, key.to_s))
|
244
|
+
end
|
245
|
+
|
246
|
+
def _transform_key(inflector, key)
|
249
247
|
case @_transform_type # rubocop:disable Style/MissingElse
|
250
248
|
when :camel then inflector.camelize(key)
|
251
249
|
when :lower_camel then inflector.camelize_lower(key)
|
@@ -259,7 +257,8 @@ module Alba
|
|
259
257
|
when Symbol then fetch_attribute_from_object_and_resource(obj, attribute)
|
260
258
|
when Proc then instance_exec(obj, &attribute)
|
261
259
|
when Alba::Association then yield_if_within(attribute.name.to_sym) { |within| attribute.to_h(obj, params: params, within: within) }
|
262
|
-
when TypedAttribute
|
260
|
+
when TypedAttribute then attribute.value(obj)
|
261
|
+
when NestedAttribute then attribute.value(object: obj, params: params)
|
263
262
|
when ConditionalAttribute then attribute.with_passing_condition(resource: self, object: obj) { |attr| fetch_attribute(obj, key, attr) }
|
264
263
|
else
|
265
264
|
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
@@ -328,8 +327,9 @@ module Alba
|
|
328
327
|
|
329
328
|
def assign_attributes(attrs, if_value)
|
330
329
|
attrs.each do |attr_name|
|
331
|
-
|
332
|
-
|
330
|
+
attr_name = attr_name.to_sym
|
331
|
+
attr = if_value ? ConditionalAttribute.new(body: attr_name, condition: if_value) : attr_name
|
332
|
+
@_attributes[attr_name] = attr
|
333
333
|
end
|
334
334
|
end
|
335
335
|
private :assign_attributes
|
@@ -442,8 +442,8 @@ module Alba
|
|
442
442
|
|
443
443
|
# Set layout
|
444
444
|
#
|
445
|
-
# @
|
446
|
-
# @
|
445
|
+
# @param file [String] name of the layout file
|
446
|
+
# @param inline [Proc] a proc returning JSON string or a Hash representing JSON
|
447
447
|
def layout(file: nil, inline: nil)
|
448
448
|
@_layout = Layout.new(file: file, inline: inline)
|
449
449
|
end
|
data/lib/alba/version.rb
CHANGED
data/lib/alba.rb
CHANGED
@@ -4,6 +4,8 @@ require_relative 'alba/errors'
|
|
4
4
|
require_relative 'alba/resource'
|
5
5
|
require_relative 'alba/deprecation'
|
6
6
|
|
7
|
+
require_relative 'alba/railtie' if defined?(Rails::Railtie)
|
8
|
+
|
7
9
|
# Core module
|
8
10
|
module Alba
|
9
11
|
class << self
|
@@ -42,13 +44,23 @@ module Alba
|
|
42
44
|
# @param block [Block] resource block
|
43
45
|
# @return [String] serialized JSON string
|
44
46
|
# @raise [ArgumentError] if block is absent or `with` argument's type is wrong
|
45
|
-
def serialize(object, root_key: nil, &block)
|
46
|
-
|
47
|
-
|
48
|
-
resource = klass.new(object)
|
47
|
+
def serialize(object = nil, root_key: nil, &block)
|
48
|
+
resource = resource_with(object, &block)
|
49
49
|
resource.serialize(root_key: root_key)
|
50
50
|
end
|
51
51
|
|
52
|
+
# Hashify the object with inline definitions
|
53
|
+
#
|
54
|
+
# @param object [Object] the object to be serialized
|
55
|
+
# @param root_key [Symbol, nil, true]
|
56
|
+
# @param block [Block] resource block
|
57
|
+
# @return [String] serialized JSON string
|
58
|
+
# @raise [ArgumentError] if block is absent or `with` argument's type is wrong
|
59
|
+
def hashify(object = nil, root_key: nil, &block)
|
60
|
+
resource = resource_with(object, &block)
|
61
|
+
resource.as_json(root_key: root_key)
|
62
|
+
end
|
63
|
+
|
52
64
|
# Enable inference for key and resource name
|
53
65
|
#
|
54
66
|
# @param with [Symbol, Class, Module] inflector
|
@@ -105,16 +117,45 @@ module Alba
|
|
105
117
|
const_parent.const_get("#{inflector.classify(name)}Resource")
|
106
118
|
end
|
107
119
|
|
120
|
+
# Configure Alba to symbolize keys
|
121
|
+
def symbolize_keys!
|
122
|
+
@symbolize_keys = true
|
123
|
+
end
|
124
|
+
|
125
|
+
# Configure Alba to stringify (not symbolize) keys
|
126
|
+
def stringify_keys!
|
127
|
+
@symbolize_keys = false
|
128
|
+
end
|
129
|
+
|
130
|
+
# Regularize key to be either Symbol or String depending on @symbolize_keys
|
131
|
+
# Returns nil if key is nil
|
132
|
+
#
|
133
|
+
# @param key [String, Symbol, nil]
|
134
|
+
# @return [Symbol, String, nil]
|
135
|
+
def regularize_key(key)
|
136
|
+
return if key.nil?
|
137
|
+
|
138
|
+
@symbolize_keys ? key.to_sym : key.to_s
|
139
|
+
end
|
140
|
+
|
108
141
|
# Reset config variables
|
109
142
|
# Useful for test cleanup
|
110
143
|
def reset!
|
111
144
|
@encoder = default_encoder
|
145
|
+
@symbolize_keys = false
|
112
146
|
@_on_error = :raise
|
113
147
|
@_on_nil = nil
|
114
148
|
end
|
115
149
|
|
116
150
|
private
|
117
151
|
|
152
|
+
# This method could be part of public API, but for now it's private
|
153
|
+
def resource_with(object, &block)
|
154
|
+
klass = block ? resource_class(&block) : infer_resource_class(object.class.name)
|
155
|
+
|
156
|
+
klass.new(object)
|
157
|
+
end
|
158
|
+
|
118
159
|
def inflector_from(name_or_module)
|
119
160
|
case name_or_module
|
120
161
|
when nil then nil
|
@@ -124,8 +165,7 @@ module Alba
|
|
124
165
|
when :dry
|
125
166
|
require 'dry/inflector'
|
126
167
|
Dry::Inflector.new
|
127
|
-
else
|
128
|
-
validate_inflector(name_or_module)
|
168
|
+
else validate_inflector(name_or_module)
|
129
169
|
end
|
130
170
|
end
|
131
171
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: alba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OKURA Masafumi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Alba is the fastest JSON serializer for Ruby. It focuses on performance,
|
14
14
|
flexibility and usability.
|
@@ -59,6 +59,7 @@ files:
|
|
59
59
|
- lib/alba/errors.rb
|
60
60
|
- lib/alba/layout.rb
|
61
61
|
- lib/alba/nested_attribute.rb
|
62
|
+
- lib/alba/railtie.rb
|
62
63
|
- lib/alba/resource.rb
|
63
64
|
- lib/alba/typed_attribute.rb
|
64
65
|
- lib/alba/version.rb
|
@@ -83,14 +84,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
83
84
|
requirements:
|
84
85
|
- - ">="
|
85
86
|
- !ruby/object:Gem::Version
|
86
|
-
version: 2.
|
87
|
+
version: 2.7.0
|
87
88
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
89
|
requirements:
|
89
90
|
- - ">="
|
90
91
|
- !ruby/object:Gem::Version
|
91
92
|
version: '0'
|
92
93
|
requirements: []
|
93
|
-
rubygems_version: 3.
|
94
|
+
rubygems_version: 3.4.10
|
94
95
|
signing_key:
|
95
96
|
specification_version: 4
|
96
97
|
summary: Alba is the fastest JSON serializer for Ruby.
|