alba 3.1.0 → 3.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0bea04ee2e971c750f9d62eca519a5401e48ab1361199581bad50f3a89e7544
4
- data.tar.gz: 726fc7d31243362d2ef4165a3784a1a36eb8f067af00fc9c25664ff1c15650ac
3
+ metadata.gz: cabbda58cec9e9d6d1c96149f6d72d589a653f3db8112781a8a5267e4d3a5c25
4
+ data.tar.gz: b670cb0a6d034da44cab1f267a32e44fe2e99defd3d4318cbe03efc6bf02c8f4
5
5
  SHA512:
6
- metadata.gz: f249c1974afc00291145059d9fcb61250948138d0ccbf33cadf1b221be1318c3425b24a0a12b16233452363ec7eda292da3a798a3534465eafba9594740e301c
7
- data.tar.gz: 3e5e3e6d6c5889fa26adf5f3164bac67f7904de765e55e67c3290e38024838bbd2439a1e4e69e2b284dd39624eb5094fd8740cc15ca56794ca1d5fdbd76d8288
6
+ metadata.gz: 350fd6d656ef37072e22033cb77cf9bb50f613f53392fcb20a0bc229ceeaa4a90f03ad41f75e1176f1adca65bb5a2eeffef13c33e60d288a71600fbaa5fb2572
7
+ data.tar.gz: 494d0202f252fd01613ba3621284e8d100d25df5d0e73e8732d4aaddc989ecf05282320c6f24c38548cd7ebc1d45920a44da6abaa09fb9efe7b180898d0ffdf3
@@ -12,7 +12,7 @@ jobs:
12
12
  fail-fast: false
13
13
  matrix:
14
14
  os: [ubuntu-latest, windows-latest, macos-latest]
15
- ruby: ['3.0', 3.1, 3.2, head, truffleruby] # TODO: Add jruby after the error is resolved
15
+ ruby: ['3.0', 3.1, 3.2, 3.3, head, jruby, truffleruby]
16
16
  gemfile: [all, without_active_support, without_oj]
17
17
  exclude:
18
18
  - os: windows-latest
@@ -4,18 +4,13 @@ on: [pull_request]
4
4
 
5
5
  jobs:
6
6
  build:
7
- strategy:
8
- fail-fast: false
9
- matrix:
10
- ruby: ['3.0', 3.1, 3.2]
11
7
  runs-on: ubuntu-latest
12
8
  steps:
13
9
  - uses: actions/checkout@v4
14
10
  - name: Set up Ruby
15
11
  uses: ruby/setup-ruby@v1
16
12
  with:
17
- ruby-version: ${{ matrix.ruby }}
18
- bundler-cache: true
13
+ ruby-version: 3.3
19
14
  - name: Run benchmark
20
15
  run: |
21
16
  ruby script/perf_check.rb
data/.rubocop.yml CHANGED
@@ -88,6 +88,11 @@ Minitest/NoTestCases:
88
88
  Exclude:
89
89
  - 'test/dependencies/test_dependencies.rb'
90
90
 
91
+ # We need to use `OpenStruct` to wrap object in ConfitionalAttribute
92
+ Performance/OpenStruct:
93
+ Exclude:
94
+ - 'lib/alba/conditional_attribute.rb'
95
+
91
96
  # We need to eval resource code to test errors on resource classes
92
97
  Security/Eval:
93
98
  Exclude:
@@ -116,10 +121,6 @@ Style/DocumentationMethod:
116
121
  Exclude:
117
122
  - 'README.md'
118
123
 
119
- # This might be true in the future, but not many good things
120
- Style/FrozenStringLiteralComment:
121
- Enabled: false
122
-
123
124
  # I don't want to think about error class in example code
124
125
  Style/ImplicitRuntimeError:
125
126
  Exclude:
@@ -140,6 +141,11 @@ Style/MethodCallWithArgsParentheses:
140
141
  Style/MissingElse:
141
142
  EnforcedStyle: case
142
143
 
144
+ # We need to use `OpenStruct` to wrap object in ConfitionalAttribute
145
+ Style/OpenStructUse:
146
+ Exclude:
147
+ - 'lib/alba/conditional_attribute.rb'
148
+
143
149
  # It's example code, please forgive us
144
150
  Style/OptionalBooleanParameter:
145
151
  Exclude:
data/CHANGELOG.md CHANGED
@@ -1,11 +1,24 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.3.0] 2024-10-09
10
+
11
+ ### Added
12
+
13
+ - Add `ArrayOfString` and `ArrayOfInteger` type [#378](https://github.com/okuramasafumi/alba/pull/378)
14
+
15
+ ## [3.2.0] 2024-06-21
16
+
17
+ ### Added
18
+
19
+ - Rails controller integration [#370](https://github.com/okuramasafumi/alba/pull/370)
20
+ - Modification API: `transform_keys!` [#372](https://github.com/okuramasafumi/alba/pull/372)
21
+
9
22
  ## [3.1.0] 2024-03-23
10
23
 
11
24
  ### Added
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in alba.gemspec
@@ -9,13 +11,12 @@ gem 'ffaker', require: false # For testing
9
11
  gem 'minitest', '~> 5.14' # For test
10
12
  gem 'railties', require: false # For Rails integration testing
11
13
  gem 'rake', '~> 13.0' # For test and automation
12
- gem 'rubocop', '~> 1.62.0', require: false # For lint
14
+ gem 'rubocop', '~> 1.66.1', require: false # For lint
13
15
  gem 'rubocop-gem_dev', '>= 0.3.0', require: false # For lint
14
16
  gem 'rubocop-md', '~> 1.0', require: false # For lint
15
- gem 'rubocop-minitest', '~> 0.35.0', require: false # For lint
16
- gem 'rubocop-performance', '~> 1.20.2', require: false # For lint
17
+ gem 'rubocop-minitest', '~> 0.36.0', require: false # For lint
18
+ gem 'rubocop-performance', '~> 1.22.1', require: false # For lint
17
19
  gem 'rubocop-rake', '~> 0.6.0', require: false # For lint
18
- gem 'ruby-lsp', require: false # For language server
19
20
  gem 'simplecov', '~> 0.22.0', require: false # For test coverage
20
21
  gem 'simplecov-cobertura', require: false # For test coverage
21
22
  # gem 'steep', require: false # For language server and typing
data/README.md CHANGED
@@ -92,13 +92,13 @@ Alba is easy to use because there are only a few methods to remember. It's also
92
92
 
93
93
  ### Feature rich
94
94
 
95
- 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).
95
+ 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).
96
96
 
97
97
  ### Other reasons
98
98
 
99
99
  - Dependency free, no need to install `oj` or `activesupport` while Alba works well with them
100
100
  - Well tested, the test coverage is 99%
101
- - Well maintained, gettings frequent update and new releases (see [version history](https://rubygems.org/gems/alba/versions))
101
+ - Well maintained, getting frequent update and new releases (see [version history](https://rubygems.org/gems/alba/versions))
102
102
 
103
103
  ## Installation
104
104
 
@@ -317,7 +317,7 @@ end
317
317
  FooResource.new(Foo.new).serialize
318
318
  ```
319
319
 
320
- By default, Alba create the JSON as `'{"bar":"This is FooResource"}'`. This means Alba calls a method on a Resource class and doesn't call a method on a target object. This rule is applied to methods that are explicitly defined on Resource class, so methods that Resource class inherits from `Object` class such as `format` are ignored.
320
+ By default, Alba creates the JSON as `'{"bar":"This is FooResource"}'`. This means Alba calls a method on a Resource class and doesn't call a method on a target object. This rule is applied to methods that are explicitly defined on Resource class, so methods that Resource class inherits from `Object` class such as `format` are ignored.
321
321
 
322
322
  ```ruby
323
323
  class Foo
@@ -363,7 +363,7 @@ end
363
363
  FooResource.new(Foo.new).serialize
364
364
  # => '{"bar":"This is Foo"}'
365
365
  ```
366
-
366
+
367
367
  #### Params
368
368
 
369
369
  You can pass a Hash to the resource for internal use. It can be used as "flags" to control attribute content.
@@ -1023,6 +1023,36 @@ user = User.new(1, nil, nil)
1023
1023
  UserResource.new(user).serialize # => '{"id":1}'
1024
1024
  ```
1025
1025
 
1026
+ #### Caution for the second parameter in `if` proc
1027
+
1028
+ `if` proc takes two parameters. The first one is the target object, `user` in the example above. The second one is `attribute` representing each attribute `if` option affects. Note that it actually calls attribute methods, so you cannot use it to prevent attribute methods called. This means if the target object is an `ActiveRecord::Base` object and using `association` with `if` option, you might want to skip the second parameter so that the SQL query won't be issued.
1029
+
1030
+ Example:
1031
+
1032
+ ```ruby
1033
+ class User < ApplicationRecord
1034
+ has_many :posts
1035
+ end
1036
+
1037
+ class Post < ApplicationRecord
1038
+ belongs_to :user
1039
+ end
1040
+
1041
+ class UserResource
1042
+ include Alba::Resource
1043
+
1044
+ # Since `_posts` parameter exists, `user.posts` are loaded
1045
+ many :posts, if: proc { |user, _posts| user.admin? }
1046
+ end
1047
+
1048
+ class UserResource2
1049
+ include Alba::Resource
1050
+
1051
+ # Since `_posts` parameter doesn't exist, `user.posts` are NOT loaded
1052
+ many :posts, if: proc { |user| user.admin? && params[:include_post] }
1053
+ end
1054
+ ```
1055
+
1026
1056
  ### Default
1027
1057
 
1028
1058
  Alba doesn't support default value for attributes, but it's easy to set a default value.
@@ -1255,7 +1285,7 @@ UserResourceWithDifferentMetaKey.new([user]).serialize
1255
1285
  # => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"my_meta":{"foo":"bar"}}'
1256
1286
 
1257
1287
  UserResourceWithDifferentMetaKey.new([user]).serialize(meta: {extra: 42})
1258
- # => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"meta":{"size":1,"extra":42}}'
1288
+ # => '{"users":[{"id":1,"name":"Masafumi OKURA"}],"my_meta":{"foo":"bar","extra":42}}'
1259
1289
 
1260
1290
  class UserResourceChangingMetaKeyOnly
1261
1291
  include Alba::Resource
@@ -1379,6 +1409,12 @@ end
1379
1409
 
1380
1410
  You now get `created_at` attribute with `iso8601` format!
1381
1411
 
1412
+ #### Generating TypeScript types with typelizer gem
1413
+
1414
+ We often want TypeScript types corresponding to serializers. That's possible with [typelizer](https://github.com/skryukov/typelizer) gem.
1415
+
1416
+ For more information, please read its README.
1417
+
1382
1418
  ### Collection serialization into Hash
1383
1419
 
1384
1420
  Sometimes we want to serialize a collection into a Hash, not an Array. It's possible with Alba.
@@ -1517,7 +1553,36 @@ end
1517
1553
 
1518
1554
  Within `helper` block, all methods should be defined without `self.`.
1519
1555
 
1520
- `helper`
1556
+ ### Experimental: modification API
1557
+
1558
+ Alba now provides an experimental API to modify existing resource class without adding new classes. Currently only `transform_keys!` is implemented.
1559
+
1560
+ Modification API returns a new class with given modifications. It's useful when you want lots of resource classes with small changes. See it in action:
1561
+
1562
+ ```ruby
1563
+ class FooResource
1564
+ include Alba::Resource
1565
+
1566
+ transform_keys :camel
1567
+
1568
+ attributes :id
1569
+ end
1570
+
1571
+ # Rails app
1572
+ class FoosController < ApplicationController
1573
+ def index
1574
+ foos = Foo.where(some: :condition)
1575
+ key_transformation_type = params[:key_transformation_type] # Say it's "lower_camel"
1576
+ # When params is absent, do not use modification API since it's slower
1577
+ resource_class = key_transformation_type ? FooResource.transform_keys!(key_transformation_type) : FooResource
1578
+ render json: resource_class.new(foos).serialize # The keys are lower_camel
1579
+ end
1580
+ end
1581
+ ```
1582
+
1583
+ The point is that there's no need to define classes for each key transformation type (dash, camel, lower_camel and snake). This gives even more flexibility.
1584
+
1585
+ There are some drawbacks with this approach. For example, it creates an internal, anonymous class when it's called, so there is a performance penalty and debugging difficulty. It's recommended to define classes manually when you don't need high flexibility.
1521
1586
 
1522
1587
  ### Caching
1523
1588
 
@@ -1659,6 +1724,20 @@ class BarResource
1659
1724
  end
1660
1725
  ```
1661
1726
 
1727
+ You can also pass options to your helpers.
1728
+
1729
+ ```ruby
1730
+ module AlbaExtension
1731
+ def time_attributes(*attrs, **options)
1732
+ attrs.each do |attr|
1733
+ attribute(attr, **options) do |object|
1734
+ object.__send__(attr).iso8601
1735
+ end
1736
+ end
1737
+ end
1738
+ end
1739
+ ```
1740
+
1662
1741
  ### Debugging
1663
1742
 
1664
1743
  Debugging is not easy. If you find Alba not working as you expect, there are a few things to do:
@@ -1695,7 +1774,7 @@ module Logging
1695
1774
  # `...` was added in Ruby 2.7
1696
1775
  def serialize(...)
1697
1776
  puts serializable_hash
1698
- super(...)
1777
+ super
1699
1778
  end
1700
1779
  end
1701
1780
 
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rake/testtask'
3
5
 
data/benchmark/README.md CHANGED
@@ -10,76 +10,110 @@ Machine spec:
10
10
 
11
11
  |Key|Value|
12
12
  |---|---|
13
- |OS|macOS 13.2.1|
14
- |CPU|Intel Corei7 Quad Core 2.3Ghz|
15
- |RAM|32GB|
16
- |Ruby|ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-darwin21]|
13
+ |OS|macOS 14.7|
14
+ |CPU|Apple M1 Pro|
15
+ |RAM|16GB|
16
+ |Ruby|ruby 3.3.5 (2024-09-03 revision ef084cc8f4) [arm64-darwin23]|
17
17
 
18
18
  Library versions:
19
19
 
20
20
  |Library|Version|
21
21
  |---|---|
22
- |alba|2.2.0|
23
- |blueprinter|0.25.3|
22
+ |alba|3.2.0|
23
+ |blueprinter|1.1.0|
24
24
  |fast_serializer_ruby|0.6.9|
25
25
  |jserializer|0.2.1|
26
- |oj|3.14.2|
26
+ |oj|3.16.6|
27
27
  |simple_ams|0.2.6|
28
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|
29
+ |turbostreamer|1.11.0|
30
+ |jbuilder|2.13.0|
31
+ |panko_serializer|0.8.2|
32
+ |active_model_serializers|0.10.14|
33
33
 
34
34
  `benchmark-ips` with `Oj.optimize_rails`:
35
35
 
36
36
  ```
37
37
  Comparison:
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
38
+ panko: 447.0 i/s
39
+ jserializer: 168.9 i/s - 2.65x slower
40
+ alba_inline: 149.4 i/s - 2.99x slower
41
+ alba: 146.5 i/s - 3.05x slower
42
+ turbostreamer: 138.7 i/s - 3.22x slower
43
+ rails: 105.6 i/s - 4.23x slower
44
+ fast_serializer: 97.6 i/s - 4.58x slower
45
+ blueprinter: 66.7 i/s - 6.70x slower
46
+ representable: 50.6 i/s - 8.83x slower
47
+ simple_ams: 35.5 i/s - 12.57x slower
48
+ ams: 14.8 i/s - 30.25x slower
49
49
  ```
50
50
 
51
51
  `benchmark-ips` without `Oj.optimize_rails`:
52
52
 
53
53
  ```
54
54
  Comparison:
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
55
+ panko: 457.9 i/s
56
+ jserializer: 165.9 i/s - 2.76x slower
57
+ alba: 160.1 i/s - 2.86x slower
58
+ alba_inline: 158.5 i/s - 2.89x slower
59
+ turbostreamer: 141.7 i/s - 3.23x slower
60
+ fast_serializer: 96.2 i/s - 4.76x slower
61
+ rails: 87.2 i/s - 5.25x slower
62
+ blueprinter: 67.4 i/s - 6.80x slower
63
+ representable: 43.4 i/s - 10.55x slower
64
+ simple_ams: 34.7 i/s - 13.20x slower
65
+ ams: 14.2 i/s - 32.28x slower
66
+ ```
67
+
68
+ `benchmark-ips` with `Oj.optimize_rail` and YJIT:
69
+
70
+ ```
71
+ Comparison:
72
+ panko: 676.6 i/s
73
+ jserializer: 285.3 i/s - 2.37x slower
74
+ turbostreamer: 264.2 i/s - 2.56x slower
75
+ alba: 258.9 i/s - 2.61x slower
76
+ fast_serializer: 179.0 i/s - 3.78x slower
77
+ rails: 150.7 i/s - 4.49x slower
78
+ alba_inline: 131.5 i/s - 5.15x slower
79
+ blueprinter: 110.0 i/s - 6.15x slower
80
+ representable: 73.5 i/s - 9.21x slower
81
+ simple_ams: 62.8 i/s - 10.77x slower
82
+ ams: 20.4 i/s - 33.10x slower
83
+ ```
84
+
85
+ `benchmark-ips` with YJIT and without `Oj.optimize_rail`:
86
+
87
+ ```
88
+ Comparison:
89
+ panko: 701.9 i/s
90
+ alba: 311.1 i/s - 2.26x slower
91
+ jserializer: 281.6 i/s - 2.49x slower
92
+ turbostreamer: 240.4 i/s - 2.92x slower
93
+ fast_serializer: 180.5 i/s - 3.89x slower
94
+ alba_inline: 135.6 i/s - 5.18x slower
95
+ rails: 131.4 i/s - 5.34x slower
96
+ blueprinter: 110.7 i/s - 6.34x slower
97
+ representable: 70.5 i/s - 9.96x slower
98
+ simple_ams: 57.3 i/s - 12.24x slower
99
+ ams: 20.3 i/s - 34.51x slower
66
100
  ```
67
101
 
68
102
  `benchmark-memory`:
69
103
 
70
104
  ```
71
105
  Comparison:
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
106
+ panko: 259178 allocated
107
+ turbostreamer: 817800 allocated - 3.16x more
108
+ jserializer: 826425 allocated - 3.19x more
109
+ alba: 846465 allocated - 3.27x more
110
+ alba_inline: 867361 allocated - 3.35x more
111
+ fast_serializer: 1474345 allocated - 5.69x more
112
+ rails: 2265905 allocated - 8.74x more
113
+ blueprinter: 2469905 allocated - 9.53x more
114
+ representable: 4994281 allocated - 19.27x more
115
+ ams: 5233265 allocated - 20.19x more
116
+ simple_ams: 9506817 allocated - 36.68x more
83
117
  ```
84
118
 
85
119
  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.
@@ -221,7 +221,6 @@ blueprinter = Proc.new { PostBlueprint.render(posts) }
221
221
  fast_serializer = Proc.new { FastSerializerPostResource.new(posts).to_json }
222
222
  jserializer = Proc.new { JserializerPostSerializer.new(posts, is_collection: true).to_json }
223
223
  panko = proc { Panko::ArraySerializer.new(posts, each_serializer: PankoPostSerializer).to_json }
224
- primalize = proc { PrimalizePostsResource.new(posts: posts).to_json }
225
224
  rails = Proc.new do
226
225
  ActiveSupport::JSON.encode(posts.map{ |post| post.serializable_hash(include: :comments) })
227
226
  end
@@ -253,8 +252,7 @@ end
253
252
 
254
253
  # --- Run the benchmarks ---
255
254
 
256
- require 'benchmark/ips'
257
- Benchmark.ips do |x|
255
+ benchmark_body = lambda do |x|
258
256
  x.report(:alba, &alba)
259
257
  x.report(:alba_inline, &alba_inline)
260
258
  x.report(:ams, &ams)
@@ -270,20 +268,8 @@ Benchmark.ips do |x|
270
268
  x.compare!
271
269
  end
272
270
 
271
+ require 'benchmark/ips'
272
+ Benchmark.ips(&benchmark_body)
273
273
 
274
274
  require 'benchmark/memory'
275
- Benchmark.memory do |x|
276
- x.report(:alba, &alba)
277
- x.report(:alba_inline, &alba_inline)
278
- x.report(:ams, &ams)
279
- x.report(:blueprinter, &blueprinter)
280
- x.report(:fast_serializer, &fast_serializer)
281
- x.report(:jserializer, &jserializer)
282
- x.report(:panko, &panko)
283
- x.report(:rails, &rails)
284
- x.report(:representable, &representable)
285
- x.report(:simple_ams, &simple_ams)
286
- x.report(:turbostreamer, &turbostreamer)
287
-
288
- x.compare!
289
- end
275
+ Benchmark.memory(&benchmark_body)
data/benchmark/prep.rb CHANGED
@@ -1,33 +1,7 @@
1
- # --- Bundle dependencies ---
2
-
3
- require "bundler/inline"
4
-
5
- gemfile(true) do
6
- source "https://rubygems.org"
7
- git_source(:github) { |repo| "https://github.com/#{repo}.git" }
8
-
9
- gem "active_model_serializers"
10
- gem "activerecord", "6.1.3"
11
- gem "alba", path: '../'
12
- gem "benchmark-ips"
13
- gem "benchmark-memory"
14
- gem "blueprinter"
15
- gem "fast_serializer_ruby"
16
- gem "jbuilder"
17
- gem 'turbostreamer'
18
- gem "jserializer"
19
- gem "multi_json"
20
- gem "panko_serializer"
21
- gem "pg"
22
- gem "primalize"
23
- gem "oj"
24
- gem "representable"
25
- gem "simple_ams"
26
- gem "sqlite3"
27
- end
28
-
29
1
  # --- Test data model setup ---
30
2
 
3
+ RubyVM::YJIT.enable if ENV["YJIT"]
4
+ require "csv"
31
5
  require "pg"
32
6
  require "active_record"
33
7
  require "active_record/connection_adapters/postgresql_adapter"
data/bin/console CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'alba'
data/docs/rails.md CHANGED
@@ -44,3 +44,13 @@ render json: FooResource.new(foo), only: [:id]
44
44
  # This is OK
45
45
  render json: FooResource.new(foo), status: 200
46
46
  ```
47
+
48
+ ### Shorthand for rendering JSON with Alba
49
+
50
+ Now you can render JSON with shorthand.
51
+
52
+ First, using `render json: serialize(target)` renders JSON for given target object. You can pass `with: SomeSerializer` option to render with `SomeSerializer` in this case. If you skip `with` option Alba tries to find the correct serialize automatically.
53
+
54
+ There is a shorter version: `render_serialized_json(target)`. It also accepts `with` option.
55
+
56
+ It's recommended to use `with` option now since it cannot automatically find correct serializers sometimes.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gem 'minitest', '~> 5.14' # For test
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gem 'activesupport', require: false # For backend
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alba
2
4
  # Representing association
3
5
  # @api private
@@ -29,6 +31,11 @@ module Alba
29
31
  assign_resource(nesting, key_transformation, block, helper)
30
32
  end
31
33
 
34
+ # This is the same API in `NestedAttribute`
35
+ def key_transformation=(type)
36
+ @resource.transform_keys(type) unless @resource.is_a?(Proc)
37
+ end
38
+
32
39
  # Recursively converts an object into a Hash
33
40
  #
34
41
  # @param target [Object] the object having an association method
@@ -65,13 +72,9 @@ module Alba
65
72
  end
66
73
  end
67
74
 
68
- def assign_resource(nesting, key_transformation, block, helper) # rubocop:disable Metrics/MethodLength
75
+ def assign_resource(nesting, key_transformation, block, helper)
69
76
  @resource = if block
70
- klass = Alba.resource_class
71
- klass.helper(helper) if helper
72
- klass.transform_keys(key_transformation)
73
- klass.class_eval(&block)
74
- klass
77
+ charged_resource_class(helper, key_transformation, block)
75
78
  elsif Alba.inflector
76
79
  Alba.infer_resource_class(@name, nesting: nesting)
77
80
  else
@@ -79,6 +82,14 @@ module Alba
79
82
  end
80
83
  end
81
84
 
85
+ def charged_resource_class(helper, key_transformation, block)
86
+ klass = Alba.resource_class
87
+ klass.helper(helper) if helper
88
+ klass.transform_keys(key_transformation)
89
+ klass.class_eval(&block)
90
+ klass
91
+ end
92
+
82
93
  def to_h_with_each_resource(object, within, params)
83
94
  object.map do |item|
84
95
  @resource.call(item).new(item, within: within, params: params).to_h
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'association'
2
4
  require_relative 'constants'
3
5
  require 'ostruct'
@@ -51,8 +53,6 @@ module Alba
51
53
 
52
54
  # OpenStruct is used as a simple solution for converting Hash or Array of Hash into an object
53
55
  # Using OpenStruct is not good in general, but in this case there's no other solution
54
- # rubocop:disable Style/OpenStructUse
55
- # rubocop:disable Performance/OpenStruct
56
56
  def objectize(fetched_attribute)
57
57
  return fetched_attribute unless @body.is_a?(Alba::Association)
58
58
 
@@ -64,7 +64,5 @@ module Alba
64
64
  OpenStruct.new(fetched_attribute)
65
65
  end
66
66
  end
67
- # rubocop:enable Style/OpenStructUse
68
- # rubocop:enable Performance/OpenStruct
69
67
  end
70
68
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file includes public constants to prevent circular dependencies.
2
4
  module Alba
3
5
  REMOVE_KEY = Object.new.freeze # A constant to remove key from serialized JSON
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  begin
2
4
  require 'active_support/inflector'
3
5
  require 'active_support/core_ext/module/delegation'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alba
2
4
  # Module for printing deprecation warning
3
5
  # @api private
data/lib/alba/errors.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alba
2
4
  # Base class for Errors
3
5
  class Error < StandardError; end
data/lib/alba/layout.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
4
  require 'forwardable'
3
5
 
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alba
2
4
  # Representing nested attribute
3
5
  # @api private
4
6
  class NestedAttribute
7
+ # Setter for key_transformation, used when it's changed after class definition
8
+ attr_writer :key_transformation
9
+
5
10
  # @param key_transformation [Symbol] determines how to transform keys
6
11
  # @param block [Proc] class body
7
12
  def initialize(key_transformation: :none, &block)
data/lib/alba/railtie.rb CHANGED
@@ -1,8 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alba
2
4
  # Rails integration
3
5
  class Railtie < Rails::Railtie
4
6
  initializer 'alba.initialize' do
5
7
  Alba.inflector = :active_support
8
+
9
+ ActiveSupport.on_load(:action_controller) do
10
+ ActionController::Base.define_method(:serialize) do |obj, with: nil, &block|
11
+ with.nil? ? Alba.resource_with(obj, &block) : with.new(obj)
12
+ end
13
+
14
+ ActionController::Base.define_method(:render_serialized_json) do |obj, with: nil, &block|
15
+ json = with.nil? ? Alba.resource_with(obj, &block) : with.new(obj)
16
+ render json: json
17
+ end
18
+ end
6
19
  end
7
20
  end
8
21
  end
data/lib/alba/resource.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'association'
2
4
  require_relative 'conditional_attribute'
3
5
  require_relative 'constants'
@@ -22,17 +24,17 @@ module Alba
22
24
  # @private
23
25
  def self.included(base) # rubocop:disable Metrics/MethodLength
24
26
  super
25
- setup_method_body = +'private def _setup;'
26
27
  base.class_eval do
27
28
  # Initialize
29
+ setup_method_body = +'private def _setup;'
28
30
  INTERNAL_VARIABLES.each do |name, initial|
29
31
  instance_variable_set(:"@#{name}", initial.dup) unless instance_variable_defined?(:"@#{name}")
30
32
  setup_method_body << "@#{name} = self.class.#{name};"
31
33
  end
32
- base.define_method(:encode, Alba.encoder)
34
+ setup_method_body << 'end'
35
+ class_eval(setup_method_body, __FILE__, __LINE__ + 1)
36
+ define_method(:encode, Alba.encoder)
33
37
  end
34
- setup_method_body << 'end'
35
- base.class_eval(setup_method_body, __FILE__, __LINE__ + 1)
36
38
  base.include InstanceMethods
37
39
  base.extend ClassMethods
38
40
  end
@@ -234,23 +236,7 @@ module Alba
234
236
  def transform_key(key)
235
237
  return Alba.regularize_key(key) if @_transform_type == :none || key.nil? || key.empty? # We can skip transformation
236
238
 
237
- inflector = Alba.inflector
238
- raise Alba::Error, 'Inflector is nil. You must set inflector before transforming keys.' unless inflector
239
-
240
- Alba.regularize_key(_transform_key(inflector, key.to_s))
241
- end
242
-
243
- def _transform_key(inflector, key)
244
- case @_transform_type
245
- when :camel then inflector.camelize(key)
246
- when :lower_camel then inflector.camelize_lower(key)
247
- when :dash then inflector.dasherize(key)
248
- when :snake then inflector.underscore(key)
249
- else
250
- # :nocov:
251
- raise Alba::Error, "Unknown transform type: #{@_transform_type}"
252
- # :nocov:
253
- end
239
+ Alba.transform_key(key, transform_type: @_transform_type)
254
240
  end
255
241
 
256
242
  def fetch_attribute(obj, key, attribute) # rubocop:disable Metrics/CyclomaticComplexity
@@ -486,6 +472,26 @@ module Alba
486
472
  @_key_transformation_cascade = cascade
487
473
  end
488
474
 
475
+ # Transform keys as specified type AFTER the class is defined
476
+ # Note that this is an experimental API and may be removed/changed
477
+ #
478
+ # @see #transform_keys
479
+ def transform_keys!(type)
480
+ dup.class_eval do
481
+ transform_keys(type, root: @_transforming_root_key, cascade: @_key_transformation_cascade)
482
+
483
+ if @_key_transformation_cascade
484
+ # We need to update key transformation of associations and nested attributes
485
+ @_attributes.each_value do |attr|
486
+ next unless attr.is_a?(Association) || attr.is_a?(NestedAttribute)
487
+
488
+ attr.key_transformation = type
489
+ end
490
+ end
491
+ self # Return the new class
492
+ end
493
+ end
494
+
489
495
  # Sets key for collection serialization
490
496
  #
491
497
  # @param key [String, Symbol]
data/lib/alba/type.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alba
2
4
  # Representing type itself, combined with {Alba::TypedAttribute}
3
5
  class Type
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alba
2
4
  # Representing typed attributes to encapsulate logic about types
3
5
  # @api private
data/lib/alba/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alba
2
- VERSION = '3.1.0'.freeze
4
+ VERSION = '3.3.0'
3
5
  end
data/lib/alba.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
  require_relative 'alba/version'
3
5
  require_relative 'alba/errors'
@@ -142,6 +144,26 @@ module Alba
142
144
  @symbolize_keys ? key.to_sym : key.to_s
143
145
  end
144
146
 
147
+ # Transform a key with given transform_type
148
+ #
149
+ # @param key [String] a target key
150
+ # @param transform_type [Symbol] a transform type, either one of `camel`, `lower_camel`, `dash` or `snake`
151
+ # @return [String]
152
+ def transform_key(key, transform_type:)
153
+ raise Alba::Error, 'Inflector is nil. You must set inflector before transforming keys.' unless inflector
154
+
155
+ key = key.to_s
156
+
157
+ k = case transform_type
158
+ when :camel then inflector.camelize(key)
159
+ when :lower_camel then inflector.camelize_lower(key)
160
+ when :dash then inflector.dasherize(key)
161
+ when :snake then inflector.underscore(key)
162
+ else raise Alba::Error, "Unknown transform type: #{transform_type}"
163
+ end
164
+ regularize_key(k)
165
+ end
166
+
145
167
  # Register types, used for both builtin and custom types
146
168
  #
147
169
  # @see Alba::Type
@@ -170,8 +192,6 @@ module Alba
170
192
  register_default_types
171
193
  end
172
194
 
173
- private
174
-
175
195
  # This method could be part of public API, but for now it's private
176
196
  def resource_with(object, &block)
177
197
  klass = block ? resource_class(&block) : infer_resource_class(object.class.name)
@@ -179,6 +199,8 @@ module Alba
179
199
  klass.new(object)
180
200
  end
181
201
 
202
+ private
203
+
182
204
  def inflector_from(name_or_module)
183
205
  case name_or_module
184
206
  when nil then nil
@@ -239,14 +261,17 @@ module Alba
239
261
  inflector
240
262
  end
241
263
 
242
- def register_default_types
264
+ def register_default_types # rubocop:disable Mertics/AbcSize
243
265
  [String, :String].each do |t|
244
- register_type(t, check: ->(obj) { obj.is_a?(String) }, converter: ->(obj) { obj.to_s })
266
+ register_type(t, check: ->(obj) { obj.is_a?(String) }, converter: lambda(&:to_s))
245
267
  end
246
268
  [Integer, :Integer].each do |t|
247
269
  register_type(t, check: ->(obj) { obj.is_a?(Integer) }, converter: ->(obj) { Integer(obj) })
248
270
  end
249
271
  register_type(:Boolean, check: ->(obj) { [true, false].include?(obj) }, converter: ->(obj) { !!obj })
272
+ [String, Integer].each do |t|
273
+ register_type(:"ArrayOf#{t}", check: ->(d) { d.is_a?(Array) && d.all? { _1.is_a?(t) } })
274
+ end
250
275
  end
251
276
  end
252
277
 
data/script/perf_check.rb CHANGED
@@ -2,74 +2,7 @@
2
2
  # Fetch Alba from local, otherwise fetch latest from RubyGems
3
3
  # exit(status)
4
4
 
5
- # --- Bundle dependencies ---
6
-
7
- require "bundler/inline"
8
-
9
- gemfile(true) do
10
- source "https://rubygems.org"
11
- git_source(:github) { |repo| "https://github.com/#{repo}.git" }
12
-
13
- gem "activerecord", "~> 6.1.3"
14
- gem "alba", path: '../'
15
- gem "benchmark-ips"
16
- gem "blueprinter"
17
- gem "jbuilder"
18
- gem "multi_json"
19
- gem "oj"
20
- gem "sqlite3"
21
- end
22
-
23
- # --- Test data model setup ---
24
-
25
- require "active_record"
26
- require "oj"
27
- require "sqlite3"
28
- Oj.optimize_rails
29
-
30
- ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
31
-
32
- ActiveRecord::Schema.define do
33
- create_table :posts, force: true do |t|
34
- t.string :body
35
- end
36
-
37
- create_table :comments, force: true do |t|
38
- t.integer :post_id
39
- t.string :body
40
- t.integer :commenter_id
41
- end
42
-
43
- create_table :users, force: true do |t|
44
- t.string :name
45
- end
46
- end
47
-
48
- class Post < ActiveRecord::Base
49
- has_many :comments
50
- has_many :commenters, through: :comments, class_name: 'User', source: :commenter
51
-
52
- def attributes
53
- {id: nil, body: nil, commenter_names: commenter_names}
54
- end
55
-
56
- def commenter_names
57
- commenters.pluck(:name)
58
- end
59
- end
60
-
61
- class Comment < ActiveRecord::Base
62
- belongs_to :post
63
- belongs_to :commenter, class_name: 'User'
64
-
65
- def attributes
66
- {id: nil, body: nil}
67
- end
68
- end
69
-
70
- class User < ActiveRecord::Base
71
- has_many :comments
72
- end
5
+ require_relative '../benchmark/prep'
73
6
 
74
7
  # --- Alba serializers ---
75
8
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: alba
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OKURA Masafumi
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-03-22 00:00:00.000000000 Z
10
+ date: 2024-10-09 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: Alba is the fastest JSON serializer for Ruby. It focuses on performance,
14
13
  flexibility and usability.
@@ -79,7 +78,6 @@ metadata:
79
78
  documentation_uri: https://rubydoc.info/github/okuramasafumi/alba
80
79
  source_code_uri: https://github.com/okuramasafumi/alba
81
80
  rubygems_mfa_required: 'true'
82
- post_install_message:
83
81
  rdoc_options: []
84
82
  require_paths:
85
83
  - lib
@@ -94,8 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
92
  - !ruby/object:Gem::Version
95
93
  version: '0'
96
94
  requirements: []
97
- rubygems_version: 3.5.6
98
- signing_key:
95
+ rubygems_version: 3.6.0.dev
99
96
  specification_version: 4
100
97
  summary: Alba is the fastest JSON serializer for Ruby.
101
98
  test_files: []