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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86253dcf425b1a8bf489db53327a4f9a13c1ae046f87a5f558b75b9c1df18964
4
- data.tar.gz: de8b3a29bd9b12f8cd6718a04f6140bec7611bedbf654232e9119f541d7f904e
3
+ metadata.gz: 3ba9736fa95cae41e16375e21770a7244b1ee5af9787090a1dadbe3f50839221
4
+ data.tar.gz: cb06fc313a5abb4914fceb3eb044911bd556d11cfa2239b3509147f5f992ab9e
5
5
  SHA512:
6
- metadata.gz: dea93b6391ceaab7ec2759758d24df0eb14e8c59c52d8a174aeda79373afac23ca9f909a65aae3c2442106dd9d8dc700668eb6b791002ef0f9cf34833f53099a
7
- data.tar.gz: 535c33bd2bea00d3029ab07a0848e37e8a30b8c6d66c3a1f894b51ad9b4aae30fc498dd000086c3e6a851c31069e6ab5d5ce11f8d96b4c687c26ae0174d42e54
6
+ metadata.gz: 4814af5d6e0a69d34ba6f3c9ef9292e7a83196ff3d231851d799857518888e9db4bc075a0cbc88ae8e7e39aa20e724085224e1944b843edc7b685ef372cefa71
7
+ data.tar.gz: 9cb559556d0e44ea4218328d645f4666e0dd5cf63a4b380b64e7e6bf1460724ecd3e04b8eb138ac1aac30176a5d0491db1451e3bdaf2130771ab06bdbb2e4d50
data/.codeclimate.yml CHANGED
@@ -9,3 +9,4 @@ checks:
9
9
  exclude_patterns:
10
10
  - "benchmark/**/*"
11
11
  - "script/**/*"
12
+ - "test/**/*"
@@ -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.6, 2.7, 3.0, 3.1, head, jruby, truffleruby]
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.6
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
- IgnoredMethods: ['require', 'require_relative', 'include', 'extend', 'puts', 'p', 'warn', 'raise', 'send', 'public_send']
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/master/DESIGN.md) for those who want to know the internal of Alba
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-minitest', '~> 0.24.0', require: false # For lint
13
- gem 'rubocop-performance', '~> 1.15.0', require: false # For lint
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 short codebase. Finally it's easy to extend since it provides some methods for override to change default behavior of Alba.
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.6 and higher and latest JRuby and TruffleRuby.
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 == nil
137
- puts "inflector not set"
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
- proc { |articles, params, user|
350
- filter = params[:filter] || :odd?
351
- articles.select {|a| a.id.send(filter) && !user.banned }
352
- },
353
- resource: ArticleResource
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
- key: 'my_articles', # Set key here
378
- resource: ArticleResource
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: { expose_secret: false }
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 RuntimeError, 'Error!'
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) # rubocop:disable Style/OptionalBooleanParameter
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, @name = id, name
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 attr do |object|
1283
- time = object.send(attr)
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
- def serialize(...) # `...` was added in Ruby 2.7
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 Logging
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.6.0')
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 12.2.1|
13
+ |OS|macOS 13.2.1|
14
14
  |CPU|Intel Corei7 Quad Core 2.3Ghz|
15
15
  |RAM|32GB|
16
- |Ruby|ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-darwin19]|
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: 267.6 i/s
23
- rails: 111.2 i/s - 2.41x (± 0.00) slower
24
- jserializer: 106.2 i/s - 2.52x (± 0.00) slower
25
- alba: 102.8 i/s - 2.60x (± 0.00) slower
26
- turbostreamer: 99.9 i/s - 2.68x (± 0.00) slower
27
- jbuilder: 90.7 i/s - 2.95x (± 0.00) slower
28
- alba_inline: 90.0 i/s - 2.97x (± 0.00) slower
29
- primalize: 82.1 i/s - 3.26x (± 0.00) slower
30
- fast_serializer: 62.7 i/s - 4.27x (± 0.00) slower
31
- jsonapi_same_format: 59.5 i/s - 4.50x (± 0.00) slower
32
- jsonapi: 55.3 i/s - 4.84x (± 0.00) slower
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: 283.8 i/s
44
- turbostreamer: 102.9 i/s - 2.76x (± 0.00) slower
45
- alba: 102.4 i/s - 2.77x (± 0.00) slower
46
- alba_inline: 98.7 i/s - 2.87x (± 0.00) slower
47
- jserializer: 93.3 i/s - 3.04x (± 0.00) slower
48
- fast_serializer: 60.1 i/s - 4.73x (± 0.00) slower
49
- blueprinter: 53.8 i/s - 5.28x (± 0.00) slower
50
- rails: 37.1 i/s - 7.65x (± 0.00) slower
51
- jbuilder: 37.1 i/s - 7.66x (± 0.00) slower
52
- primalize: 31.2 i/s - 9.08x (± 0.00) slower
53
- jsonapi_same_format: 28.3 i/s - 10.03x (± 0.00) slower
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: 230418 allocated
65
- alba: 733217 allocated - 3.18x more
66
- alba_inline: 748297 allocated - 3.25x more
67
- turbostreamer: 781008 allocated - 3.39x more
68
- jserializer: 819705 allocated - 3.56x more
69
- primalize: 1195163 allocated - 5.19x more
70
- fast_serializer: 1232385 allocated - 5.35x more
71
- rails: 1236761 allocated - 5.37x more
72
- blueprinter: 1588937 allocated - 6.90x more
73
- jbuilder: 1774157 allocated - 7.70x more
74
- jsonapi_same_format: 2132489 allocated - 9.25x more
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` jbuilder and Rails standard serialization are also fast.
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.enable_inference!(with: :active_support)
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.
@@ -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) unless @params.empty?
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 if !with_two_arity_proc_condition
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
- # @params file [String] name of the layout file
12
- # @params inline [Proc] a proc returning JSON string or a Hash representing JSON
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
- # @return [Hash]
12
- def value(object)
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
@@ -0,0 +1,8 @@
1
+ module Alba
2
+ # Rails integration
3
+ class Railtie < Rails::Railtie
4
+ initializer 'alba.initialize' do
5
+ Alba.inflector = :active_support
6
+ end
7
+ end
8
+ end
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
- DSLS.each_key { |name| instance_variable_set("@#{name}", self.class.__send__(name)) }
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.to_s
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
- # TODO: use `filter_map` after dropping support of Ruby 2.6
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 { |item| [item.public_send(@_collection_key).to_s, converter.call(item)] }
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
- if Alba.inflector
152
- @_key_for_collection == true ? resource_name(pluralized: true) : @_key_for_collection.to_s
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
- if Alba.inflector
161
- @_key == true ? resource_name(pluralized: false) : @_key.to_s
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
- name = inflector.demodulize(class_name).delete_suffix('Resource')
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
- # @return [Symbol]
242
- def transform_key(key) # rubocop:disable Metrics/CyclomaticComplexity
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, NestedAttribute then attribute.value(obj)
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
- attr = if_value ? ConditionalAttribute.new(body: attr_name.to_sym, condition: if_value) : attr_name.to_sym
332
- @_attributes[attr_name.to_sym] = attr
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
- # @params file [String] name of the layout file
446
- # @params inline [Proc] a proc returning JSON string or a Hash representing JSON
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
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '2.1.0'.freeze
2
+ VERSION = '2.3.0'.freeze
3
3
  end
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
- klass = block ? resource_class(&block) : infer_resource_class(object.class.name)
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.1.0
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: 2022-12-03 00:00:00.000000000 Z
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.6.0
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.3.21
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.