alba 2.2.0 → 2.4.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/.editorconfig +10 -0
- data/.rubocop.yml +38 -2
- data/CHANGELOG.md +21 -0
- data/Gemfile +3 -2
- data/HACKING.md +2 -1
- data/README.md +235 -32
- data/alba.gemspec +1 -1
- data/benchmark/README.md +52 -48
- data/docs/rails.md +3 -1
- data/lib/alba/association.rb +8 -4
- data/lib/alba/conditional_attribute.rb +2 -1
- data/lib/alba/default_inflector.rb +13 -1
- data/lib/alba/deprecation.rb +2 -1
- data/lib/alba/layout.rb +1 -0
- data/lib/alba/nested_attribute.rb +6 -3
- data/lib/alba/resource.rb +71 -42
- data/lib/alba/typed_attribute.rb +1 -0
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +47 -9
- 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: 89ce66083a51c7811468f9435288afbc53f81418f21635af4393eaf1ac635d14
|
4
|
+
data.tar.gz: 7344d77ba35c156a6a18abe6f5ff498b7a8cd24b460c5b3f6bf1f0739bd0c8aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33e534a676d589ba88db064b8ba57bbd10a20d268c417d8ed89fbc9b830f2861e4d3c1b46772d9151fce5dcff909e591f26f4fe1be77c247da4e1ceb4998d1cc
|
7
|
+
data.tar.gz: 401030ee3033228b11d2b75fa411809e7707b0c8b6791c2fe9da8bf9ce290c290225cb4e23895bc31019cc8cead19ac20335fffb24682c58ebc7f09a8250999a
|
data/.codeclimate.yml
CHANGED
data/.editorconfig
ADDED
data/.rubocop.yml
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
---
|
2
2
|
|
3
3
|
inherit_gem:
|
4
|
-
rubocop-
|
4
|
+
rubocop-gem_dev: 'config/rubocop.yml'
|
5
|
+
|
6
|
+
inherit_mode:
|
7
|
+
merge:
|
8
|
+
- Exclude
|
5
9
|
|
6
10
|
require:
|
11
|
+
- rubocop-md
|
7
12
|
- rubocop-minitest
|
8
13
|
- rubocop-performance
|
9
14
|
- rubocop-rake
|
@@ -13,6 +18,7 @@ AllCops:
|
|
13
18
|
- 'Rakefile'
|
14
19
|
- 'alba.gemspec'
|
15
20
|
- 'benchmark/**/*.rb'
|
21
|
+
- 'docs/**/*'
|
16
22
|
- 'script/**/*.rb'
|
17
23
|
NewCops: enable
|
18
24
|
EnabledByDefault: true
|
@@ -22,6 +28,15 @@ AllCops:
|
|
22
28
|
Bundler/GemVersion:
|
23
29
|
Enabled: false
|
24
30
|
|
31
|
+
# Test class is a class, but not really
|
32
|
+
Layout/ClassStructure:
|
33
|
+
Exclude:
|
34
|
+
- 'test/**/*'
|
35
|
+
|
36
|
+
# LineLength 80 comes from restrictions in good old days.
|
37
|
+
Layout/LineLength:
|
38
|
+
Max: 160
|
39
|
+
|
25
40
|
# We'd like to write something like:
|
26
41
|
# assert_equal(
|
27
42
|
# expected,
|
@@ -63,6 +78,11 @@ Metrics/ParameterLists:
|
|
63
78
|
Minitest/EmptyLineBeforeAssertionMethods:
|
64
79
|
Enabled: false
|
65
80
|
|
81
|
+
# By nature of that test
|
82
|
+
Minitest/NoTestCases:
|
83
|
+
Exclude:
|
84
|
+
- 'test/dependencies/test_dependencies.rb'
|
85
|
+
|
66
86
|
# We need to eval resource code to test errors on resource classes
|
67
87
|
Security/Eval:
|
68
88
|
Exclude:
|
@@ -80,14 +100,26 @@ Style/Copyright:
|
|
80
100
|
Style/DisableCopsWithinSourceCodeDirective:
|
81
101
|
Enabled: false
|
82
102
|
|
103
|
+
Style/Documentation:
|
104
|
+
Exclude:
|
105
|
+
- 'test/**/*'
|
106
|
+
|
107
|
+
Style/DocumentationMethod:
|
108
|
+
Exclude:
|
109
|
+
- 'README.md'
|
110
|
+
|
83
111
|
Style/FrozenStringLiteralComment:
|
84
112
|
Enabled: false
|
85
113
|
|
114
|
+
Style/ImplicitRuntimeError:
|
115
|
+
Exclude:
|
116
|
+
- 'README.md'
|
117
|
+
|
86
118
|
Style/InlineComment:
|
87
119
|
Enabled: false
|
88
120
|
|
89
121
|
Style/MethodCallWithArgsParentheses:
|
90
|
-
|
122
|
+
AllowedMethods: ['require', 'require_relative', 'include', 'extend', 'puts', 'p', 'warn', 'raise', 'send', 'public_send', 'alias_method']
|
91
123
|
Exclude:
|
92
124
|
# There are so many `attributes` call without parenthese and that's absolutely fine
|
93
125
|
- 'test/**/*.rb'
|
@@ -95,3 +127,7 @@ Style/MethodCallWithArgsParentheses:
|
|
95
127
|
# There are so many cases we just want `if` expression!
|
96
128
|
Style/MissingElse:
|
97
129
|
EnforcedStyle: case
|
130
|
+
|
131
|
+
Style/OptionalBooleanParameter:
|
132
|
+
Exclude:
|
133
|
+
- 'README.md'
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## [2.4.0] 2023-08-02
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- Implement helper [#322](https://github.com/okuramasafumi/alba/pull/322)
|
14
|
+
- Add `prefer_resource_method!` [#323](https://github.com/okuramasafumi/alba/pull/323)
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
|
18
|
+
- Fix the bug of resource name inference [No PR](https://github.com/okuramasafumi/alba/commit/dab7091efa4a76ce9e538e08efa7349c296a705c)
|
19
|
+
|
20
|
+
## [2.3.0] 2023-04-24
|
21
|
+
|
22
|
+
### Added
|
23
|
+
|
24
|
+
- Add compatibility option for key [#304](https://github.com/okuramasafumi/alba/pull/304)
|
25
|
+
- It now infers resource name from Serializer [#309](https://github.com/okuramasafumi/alba/pull/309)
|
26
|
+
- `Alba.serialize` is easier to use for multiple root keys [#311](https://github.com/okuramasafumi/alba/pull/311)
|
27
|
+
- Gives access to params in nested_attribute [#312](https://github.com/okuramasafumi/alba/pull/312)
|
28
|
+
- Thank you, @GabrielErbetta
|
29
|
+
|
9
30
|
## [2.2.0] 2023-02-17
|
10
31
|
|
11
32
|
### Added
|
data/Gemfile
CHANGED
@@ -10,12 +10,13 @@ gem 'minitest', '~> 5.14' # For test
|
|
10
10
|
gem 'railties', require: false # For Rails integration testing
|
11
11
|
gem 'rake', '~> 13.0' # For test and automation
|
12
12
|
gem 'rubocop', '>= 0.79.0', require: false # For lint
|
13
|
+
gem 'rubocop-gem_dev', '>= 0.3.0', require: false # For lint
|
14
|
+
gem 'rubocop-md', '~> 1.0', require: false # For lint
|
13
15
|
gem 'rubocop-minitest', '>= 0.25.0', require: false # For lint
|
14
16
|
gem 'rubocop-performance', '>= 1.15.0', require: false # For lint
|
15
17
|
gem 'rubocop-rake', '>= 0.5.1', require: false # For lint
|
16
|
-
gem 'rubocop-sensible', '~> 0.3.0', require: false # For lint
|
17
18
|
gem 'ruby-lsp', require: false # For language server
|
18
|
-
gem 'simplecov', '~> 0.
|
19
|
+
gem 'simplecov', '~> 0.22.0', require: false # For test coverage
|
19
20
|
gem 'simplecov-cobertura', require: false # For test coverage
|
20
21
|
# gem 'steep', require: false # For language server and typing
|
21
22
|
# gem 'typeprof', require: false # For language server and typing
|
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
@@ -11,6 +11,40 @@
|
|
11
11
|
|
12
12
|
Alba is a JSON serializer for Ruby, JRuby, and TruffleRuby.
|
13
13
|
|
14
|
+
## TL;DR
|
15
|
+
|
16
|
+
Alba allows you to do something like below.
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
class User
|
20
|
+
attr_accessor :id, :name, :email
|
21
|
+
|
22
|
+
def initialize(id, name, email)
|
23
|
+
@id = id
|
24
|
+
@name = name
|
25
|
+
@email = email
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class UserResource
|
30
|
+
include Alba::Resource
|
31
|
+
|
32
|
+
root_key :user
|
33
|
+
|
34
|
+
attributes :id, :name
|
35
|
+
|
36
|
+
attribute :name_with_email do |resource|
|
37
|
+
"#{resource.name}: #{resource.email}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
|
42
|
+
UserResource.new(user).serialize
|
43
|
+
# => '{"user":{"id":1,"name":"Masafumi OKURA","name_with_email":"Masafumi OKURA: masafumi@example.com"}}'
|
44
|
+
```
|
45
|
+
|
46
|
+
Seems useful? Continue reading!
|
47
|
+
|
14
48
|
## Discussions
|
15
49
|
|
16
50
|
Alba uses [GitHub Discussions](https://github.com/okuramasafumi/alba/discussions) to openly discuss the project.
|
@@ -43,7 +77,7 @@ Alba is faster than most of the alternatives. We have a [benchmark](https://gith
|
|
43
77
|
|
44
78
|
### Easy
|
45
79
|
|
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
|
80
|
+
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.
|
47
81
|
|
48
82
|
### Feature rich
|
49
83
|
|
@@ -82,7 +116,7 @@ You can find the documentation on [RubyDoc](https://rubydoc.info/github/okuramas
|
|
82
116
|
* Error handling
|
83
117
|
* Nil handling
|
84
118
|
* Circular associations control
|
85
|
-
*
|
119
|
+
* Types for validation and conversion
|
86
120
|
* Layout
|
87
121
|
* No runtime dependencies
|
88
122
|
|
@@ -143,13 +177,27 @@ Alba.inflector = nil
|
|
143
177
|
To check if inference is enabled etc, inspect the return value of `inflector`:
|
144
178
|
|
145
179
|
```ruby
|
146
|
-
if Alba.inflector
|
147
|
-
puts
|
180
|
+
if Alba.inflector.nil?
|
181
|
+
puts 'inflector not set'
|
148
182
|
else
|
149
183
|
puts "inflector is set to #{Alba.inflector}"
|
150
184
|
end
|
151
185
|
```
|
152
186
|
|
187
|
+
### Naming
|
188
|
+
|
189
|
+
Alba tries to infer resource name from class name like the following.
|
190
|
+
|
191
|
+
|Class name|Resource name|
|
192
|
+
| --- | --- |
|
193
|
+
| FooResource | Foo |
|
194
|
+
| FooSerializer | Foo |
|
195
|
+
| FooElse | FooElse |
|
196
|
+
|
197
|
+
Resource name is used as the default name of the root key, so you might want to name it ending with "Resource" or "Serializer"
|
198
|
+
|
199
|
+
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`.
|
200
|
+
|
153
201
|
### Simple serialization with root key
|
154
202
|
|
155
203
|
You can define attributes with (yes) `attributes` macro with attribute names. If your attribute need some calculations, you can use `attribute` with block.
|
@@ -157,6 +205,7 @@ You can define attributes with (yes) `attributes` macro with attribute names. If
|
|
157
205
|
```ruby
|
158
206
|
class User
|
159
207
|
attr_accessor :id, :name, :email, :created_at, :updated_at
|
208
|
+
|
160
209
|
def initialize(id, name, email)
|
161
210
|
@id = id
|
162
211
|
@name = name
|
@@ -220,6 +269,72 @@ class UserResource
|
|
220
269
|
end
|
221
270
|
```
|
222
271
|
|
272
|
+
#### Prefer methods on resource
|
273
|
+
|
274
|
+
By default, Alba prefers methods on the object to methods on the resource. This means if you have a following situation:
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
class User
|
278
|
+
attr_accessor :id, :name, :email
|
279
|
+
|
280
|
+
def initialize(id, name, email)
|
281
|
+
@id = id
|
282
|
+
@name = name
|
283
|
+
@email = email
|
284
|
+
end
|
285
|
+
|
286
|
+
def name_with_email
|
287
|
+
"dummy!"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
class UserResource
|
292
|
+
include Alba::Resource
|
293
|
+
|
294
|
+
root_key :user, :users # Later is for plural
|
295
|
+
|
296
|
+
attributes :id, :name, :name_with_email
|
297
|
+
|
298
|
+
# Same method exists in `User` class!
|
299
|
+
# This is not called
|
300
|
+
def name_with_email(user)
|
301
|
+
"#{user.name}: #{user.email}"
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
|
306
|
+
UserResource.new(user).serialize
|
307
|
+
# => '{"user":{"id":1,"name":"Masafumi OKURA","name_with_email":"dummy!"}}'
|
308
|
+
```
|
309
|
+
|
310
|
+
You can see that `name_with_email` is now `dummy!` from `User#name_with_email`. You cna change this behavior by using `prefer_resource_method!` DSL in a resource class:
|
311
|
+
|
312
|
+
```ruby
|
313
|
+
# With the same `User` class
|
314
|
+
|
315
|
+
class UserResource
|
316
|
+
include Alba::Resource
|
317
|
+
|
318
|
+
prefer_resource_method! # This line is important
|
319
|
+
|
320
|
+
root_key :user, :users # Later is for plural
|
321
|
+
|
322
|
+
attributes :id, :name, :name_with_email
|
323
|
+
|
324
|
+
# Same method exists in `User` class!
|
325
|
+
# But now this is called!
|
326
|
+
def name_with_email(user)
|
327
|
+
"#{user.name}: #{user.email}"
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
user = User.new(1, 'Masafumi OKURA', 'masafumi@example.com')
|
332
|
+
UserResource.new(user).serialize
|
333
|
+
# => '{"user":{"id":1,"name":"Masafumi OKURA","name_with_email":"Masafumi OKURA: masafumi@example.com"}}'
|
334
|
+
```
|
335
|
+
|
336
|
+
The next major version of Alba will change this default behavior to prefer resource methods. In case you want to preserve current behavior, there's `prefer_object_method!` DSL, which does that.
|
337
|
+
|
223
338
|
#### Params
|
224
339
|
|
225
340
|
You can pass a Hash to the resource for internal use. It can be used as "flags" to control attribute content.
|
@@ -356,11 +471,11 @@ class UserResource
|
|
356
471
|
|
357
472
|
# Second proc works as a filter
|
358
473
|
many :articles,
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
474
|
+
proc { |articles, params, user|
|
475
|
+
filter = params[:filter] || :odd?
|
476
|
+
articles.select { |a| a.id.__send__(filter) && !user.banned }
|
477
|
+
},
|
478
|
+
resource: ArticleResource
|
364
479
|
end
|
365
480
|
|
366
481
|
user = User.new(1)
|
@@ -384,8 +499,8 @@ class UserResource
|
|
384
499
|
attributes :id
|
385
500
|
|
386
501
|
many :articles,
|
387
|
-
|
388
|
-
|
502
|
+
key: 'my_articles', # Set key here
|
503
|
+
resource: ArticleResource
|
389
504
|
end
|
390
505
|
UserResource.new(user).serialize
|
391
506
|
# => '{"id":1,"my_articles":[{"title":"Hello World!"}]}'
|
@@ -452,7 +567,7 @@ class FooResourceWithParamsOverride
|
|
452
567
|
|
453
568
|
root_key :foo
|
454
569
|
|
455
|
-
one :bar, resource: BarResource, params: {
|
570
|
+
one :bar, resource: BarResource, params: {expose_secret: false}
|
456
571
|
end
|
457
572
|
|
458
573
|
Baz = Struct.new(:data, :secret)
|
@@ -544,6 +659,48 @@ Alba.serialize(something)
|
|
544
659
|
|
545
660
|
Although this might be useful sometimes, it's generally recommended to define a class for Resource.
|
546
661
|
|
662
|
+
#### Inline definition for multiple root keys
|
663
|
+
|
664
|
+
While Alba doesn't directly support multiple root keys, you can simulate it with `Alba.serialize`.
|
665
|
+
|
666
|
+
```ruby
|
667
|
+
# Define foo and bar local variables here
|
668
|
+
|
669
|
+
Alba.serialize do
|
670
|
+
attribute :key1 do
|
671
|
+
FooResource.new(foo).to_h
|
672
|
+
end
|
673
|
+
|
674
|
+
attribute :key2 do
|
675
|
+
BarResource.new(bar).to_h
|
676
|
+
end
|
677
|
+
end
|
678
|
+
# => JSON containing "key1" and "key2" as root keys
|
679
|
+
```
|
680
|
+
|
681
|
+
Note that we must use `to_h`, not `serialize`, with resources.
|
682
|
+
|
683
|
+
We can also generate a JSON with multiple root keys without making any class by the combination of `Alba.serialize` and `Alba.hashify`.
|
684
|
+
|
685
|
+
```ruby
|
686
|
+
# Define foo and bar local variables here
|
687
|
+
|
688
|
+
Alba.serialize do
|
689
|
+
attribute :foo do
|
690
|
+
Alba.hashify(foo) do
|
691
|
+
attributes :id, :name # For example
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
695
|
+
attribute :bar do
|
696
|
+
Alba.hashify(bar) do
|
697
|
+
attributes :id
|
698
|
+
end
|
699
|
+
end
|
700
|
+
end
|
701
|
+
# => JSON containing "foo" and "bar" as root keys
|
702
|
+
```
|
703
|
+
|
547
704
|
### Serializable Hash
|
548
705
|
|
549
706
|
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`.
|
@@ -796,20 +953,15 @@ A custom inflector can be plugged in as follows.
|
|
796
953
|
module CustomInflector
|
797
954
|
module_function
|
798
955
|
|
799
|
-
def camelize(string)
|
800
|
-
end
|
956
|
+
def camelize(string); end
|
801
957
|
|
802
|
-
def camelize_lower(string)
|
803
|
-
end
|
958
|
+
def camelize_lower(string); end
|
804
959
|
|
805
|
-
def dasherize(string)
|
806
|
-
end
|
960
|
+
def dasherize(string); end
|
807
961
|
|
808
|
-
def underscore(string)
|
809
|
-
end
|
962
|
+
def underscore(string); end
|
810
963
|
|
811
|
-
def classify(string)
|
812
|
-
end
|
964
|
+
def classify(string); end
|
813
965
|
end
|
814
966
|
|
815
967
|
Alba.inflector = CustomInflector
|
@@ -926,7 +1078,7 @@ class User
|
|
926
1078
|
end
|
927
1079
|
|
928
1080
|
def email
|
929
|
-
raise
|
1081
|
+
raise 'Error!'
|
930
1082
|
end
|
931
1083
|
end
|
932
1084
|
|
@@ -1018,6 +1170,11 @@ UserResource.new(User.new(1)).serialize
|
|
1018
1170
|
# => '{"user":{"id":1,"name":"User1","age":20}}'
|
1019
1171
|
```
|
1020
1172
|
|
1173
|
+
Note that `on_nil` does NOT work when the given object itself is `nil`. There are a few possible ways to deal with `nil`.
|
1174
|
+
|
1175
|
+
- Use `if` statement and avoid using Alba when the object is `nil`
|
1176
|
+
- Use "Null Object" pattern
|
1177
|
+
|
1021
1178
|
### Metadata
|
1022
1179
|
|
1023
1180
|
You can set a metadata with `meta` DSL or `meta` option.
|
@@ -1122,8 +1279,10 @@ Sometimes we want to serialize a collection into a Hash, not an Array. It's poss
|
|
1122
1279
|
```ruby
|
1123
1280
|
class User
|
1124
1281
|
attr_reader :id, :name
|
1282
|
+
|
1125
1283
|
def initialize(id, name)
|
1126
|
-
@id
|
1284
|
+
@id = id
|
1285
|
+
@name = name
|
1127
1286
|
end
|
1128
1287
|
end
|
1129
1288
|
|
@@ -1179,12 +1338,12 @@ In case you don't want to have a file for layout, Alba lets you define and apply
|
|
1179
1338
|
```ruby
|
1180
1339
|
class FooResource
|
1181
1340
|
include Alba::Resource
|
1182
|
-
layout inline: proc
|
1341
|
+
layout inline: proc {
|
1183
1342
|
{
|
1184
1343
|
header: 'my header',
|
1185
1344
|
body: serializable_hash
|
1186
1345
|
}
|
1187
|
-
|
1346
|
+
}
|
1188
1347
|
end
|
1189
1348
|
```
|
1190
1349
|
|
@@ -1195,12 +1354,12 @@ You can also use a Proc which returns String, not a Hash, for an inline layout.
|
|
1195
1354
|
```ruby
|
1196
1355
|
class FooResource
|
1197
1356
|
include Alba::Resource
|
1198
|
-
layout inline: proc
|
1357
|
+
layout inline: proc {
|
1199
1358
|
%({
|
1200
1359
|
"header": "my header",
|
1201
1360
|
"body": #{serialized_json}
|
1202
1361
|
})
|
1203
|
-
|
1362
|
+
}
|
1204
1363
|
end
|
1205
1364
|
```
|
1206
1365
|
|
@@ -1208,6 +1367,49 @@ It looks similar to file layout but you must use string interpolation for method
|
|
1208
1367
|
|
1209
1368
|
Also note that we use percentage notation here to use double quotes. Using single quotes in inline string layout causes the error which might be resolved in other ways.
|
1210
1369
|
|
1370
|
+
### Helper
|
1371
|
+
|
1372
|
+
Inheritance works well in most of the cases to share behaviors. One of the exceptions is when you want to shared behaviors with inline association. For example:
|
1373
|
+
|
1374
|
+
```ruby
|
1375
|
+
class ApplicationResource
|
1376
|
+
include Alba::Resource
|
1377
|
+
|
1378
|
+
def self.with_id
|
1379
|
+
attributes :id
|
1380
|
+
end
|
1381
|
+
end
|
1382
|
+
|
1383
|
+
class LibraryResource < ApplicationResource
|
1384
|
+
with_id
|
1385
|
+
attributes :created_at
|
1386
|
+
|
1387
|
+
with_many :library_books do
|
1388
|
+
with_id # This DOES NOT work!
|
1389
|
+
attributes :created_at
|
1390
|
+
end
|
1391
|
+
end
|
1392
|
+
```
|
1393
|
+
|
1394
|
+
This doesn't work. Technically, inside of `has_many` is a separate class which doesn't inherit from the base class (`LibraryResource` in this example).
|
1395
|
+
|
1396
|
+
`helper` solves this problem. It's just a mark for methods that should be shared with inline associations.
|
1397
|
+
|
1398
|
+
```ruby
|
1399
|
+
class ApplicationResource
|
1400
|
+
include Alba::Resource
|
1401
|
+
|
1402
|
+
helper do
|
1403
|
+
def with_id
|
1404
|
+
attributes :id
|
1405
|
+
end
|
1406
|
+
end
|
1407
|
+
end
|
1408
|
+
# Now `LibraryResource` works!
|
1409
|
+
```
|
1410
|
+
|
1411
|
+
Within `helper` block, all methods should be defined without `self.`.
|
1412
|
+
|
1211
1413
|
### Caching
|
1212
1414
|
|
1213
1415
|
Currently, Alba doesn't support caching, primarily due to the behavior of `ActiveRecord::Relation`'s cache. See [the issue](https://github.com/rails/rails/issues/41784).
|
@@ -1289,8 +1491,8 @@ module AlbaExtension
|
|
1289
1491
|
# Here attrs are an Array of Symbol
|
1290
1492
|
def formatted_time_attributes(*attrs)
|
1291
1493
|
attrs.each do |attr|
|
1292
|
-
attribute
|
1293
|
-
time = object.
|
1494
|
+
attribute(attr) do |object|
|
1495
|
+
time = object.__send__(attr)
|
1294
1496
|
time.strftime('%m/%d/%Y')
|
1295
1497
|
end
|
1296
1498
|
end
|
@@ -1347,13 +1549,14 @@ Alba currently doesn't support logging directly, but you can add your own loggin
|
|
1347
1549
|
|
1348
1550
|
```ruby
|
1349
1551
|
module Logging
|
1350
|
-
|
1552
|
+
# `...` was added in Ruby 2.7
|
1553
|
+
def serialize(...)
|
1351
1554
|
puts serializable_hash
|
1352
1555
|
super(...)
|
1353
1556
|
end
|
1354
1557
|
end
|
1355
1558
|
|
1356
|
-
FooResource.prepend
|
1559
|
+
FooResource.prepend(Logging)
|
1357
1560
|
FooResource.new(foo).serialize
|
1358
1561
|
# => "{:id=>1}" is printed
|
1359
1562
|
```
|
data/alba.gemspec
CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |spec|
|
|
13
13
|
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
|
14
14
|
|
15
15
|
spec.metadata = {
|
16
|
-
'bug_tracker_uri' => 'https://github.com/okuramasafumi/issues',
|
16
|
+
'bug_tracker_uri' => 'https://github.com/okuramasafumi/alba/issues',
|
17
17
|
'changelog_uri' => 'https://github.com/okuramasafumi/alba/blob/main/CHANGELOG.md',
|
18
18
|
'documentation_uri' => 'https://rubydoc.info/github/okuramasafumi/alba',
|
19
19
|
'source_code_uri' => 'https://github.com/okuramasafumi/alba',
|
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
@@ -1,8 +1,10 @@
|
|
1
1
|
module Alba
|
2
2
|
# Representing association
|
3
|
+
# @api private
|
3
4
|
class Association
|
4
5
|
@const_cache = {}
|
5
6
|
class << self
|
7
|
+
# cache for `const_get`
|
6
8
|
attr_reader :const_cache
|
7
9
|
end
|
8
10
|
|
@@ -14,15 +16,16 @@ module Alba
|
|
14
16
|
# @param params [Hash] params override for the association
|
15
17
|
# @param nesting [String] a namespace where source class is inferred with
|
16
18
|
# @param key_transformation [Symbol] key transformation type
|
19
|
+
# @param helper [Module] helper module to include
|
17
20
|
# @param block [Block] used to define resource when resource arg is absent
|
18
|
-
def initialize(name:, condition: nil, resource: nil, params: {}, nesting: nil, key_transformation: :none, &block)
|
21
|
+
def initialize(name:, condition: nil, resource: nil, params: {}, nesting: nil, key_transformation: :none, helper: nil, &block)
|
19
22
|
@name = name
|
20
23
|
@condition = condition
|
21
24
|
@resource = resource
|
22
25
|
@params = params
|
23
26
|
return if @resource
|
24
27
|
|
25
|
-
assign_resource(nesting, key_transformation, block)
|
28
|
+
assign_resource(nesting, key_transformation, block, helper)
|
26
29
|
end
|
27
30
|
|
28
31
|
# Recursively converts an object into a Hash
|
@@ -32,7 +35,7 @@ module Alba
|
|
32
35
|
# @param params [Hash] user-given Hash for arbitrary data
|
33
36
|
# @return [Hash]
|
34
37
|
def to_h(target, within: nil, params: {})
|
35
|
-
params = params.merge(@params)
|
38
|
+
params = params.merge(@params)
|
36
39
|
@object = target.__send__(@name)
|
37
40
|
@object = @condition.call(object, params, target) if @condition
|
38
41
|
return if @object.nil?
|
@@ -59,9 +62,10 @@ module Alba
|
|
59
62
|
end
|
60
63
|
end
|
61
64
|
|
62
|
-
def assign_resource(nesting, key_transformation, block)
|
65
|
+
def assign_resource(nesting, key_transformation, block, helper) # rubocop:disable Metrics/MethodLength
|
63
66
|
@resource = if block
|
64
67
|
klass = Alba.resource_class
|
68
|
+
klass.helper(helper) if helper
|
65
69
|
klass.transform_keys(key_transformation)
|
66
70
|
klass.class_eval(&block)
|
67
71
|
klass
|
@@ -3,6 +3,7 @@ require_relative 'constants'
|
|
3
3
|
|
4
4
|
module Alba
|
5
5
|
# Represents attribute with `if` option
|
6
|
+
# @api private
|
6
7
|
class ConditionalAttribute
|
7
8
|
# @param body [Symbol, Proc, Alba::Association, Alba::TypedAttribute] real attribute wrapped with condition
|
8
9
|
# @param condition [Symbol, Proc] condition to check
|
@@ -20,7 +21,7 @@ module Alba
|
|
20
21
|
return Alba::REMOVE_KEY unless condition_passes?(resource, object)
|
21
22
|
|
22
23
|
fetched_attribute = yield(@body)
|
23
|
-
return fetched_attribute
|
24
|
+
return fetched_attribute unless with_two_arity_proc_condition
|
24
25
|
|
25
26
|
return Alba::REMOVE_KEY unless resource.instance_exec(object, attribute_from_association_body_or(fetched_attribute), &@condition)
|
26
27
|
|
@@ -2,7 +2,7 @@ begin
|
|
2
2
|
require 'active_support/inflector'
|
3
3
|
require 'active_support/core_ext/module/delegation'
|
4
4
|
rescue LoadError
|
5
|
-
raise
|
5
|
+
raise Alba::Error, 'To use default inflector, please install `ActiveSupport` gem.'
|
6
6
|
end
|
7
7
|
|
8
8
|
module Alba
|
@@ -11,6 +11,18 @@ module Alba
|
|
11
11
|
# Another is that `ActiveSupport::Inflector` doesn't have `camelize_lower` method that we want it to have, so this module works as an adapter.
|
12
12
|
module DefaultInflector
|
13
13
|
class << self
|
14
|
+
# @!method camelize(key)
|
15
|
+
# @see https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-camelize ActiveSupport::Inflector#camelize
|
16
|
+
# @!method dasherize(key)
|
17
|
+
# @see https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-dasherize ActiveSupport::Inflector#dasherize
|
18
|
+
# @!method underscore(key)
|
19
|
+
# @see https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-underscore ActiveSupport::Inflector#underscore
|
20
|
+
# @!method classify(key)
|
21
|
+
# @see https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-classify ActiveSupport::Inflector#classify
|
22
|
+
# @!method demodulize(key)
|
23
|
+
# @see https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-demodulize ActiveSupport::Inflector#demodulize
|
24
|
+
# @!method pluralize(key)
|
25
|
+
# @see https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-pluralize ActiveSupport::Inflector#pluralize
|
14
26
|
delegate :camelize, :dasherize, :underscore, :classify, :demodulize, :pluralize, to: ActiveSupport::Inflector
|
15
27
|
end
|
16
28
|
|
data/lib/alba/deprecation.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module Alba
|
2
2
|
# Module for printing deprecation warning
|
3
|
+
# @api private
|
3
4
|
module Deprecation
|
4
|
-
# Similar to {
|
5
|
+
# Similar to {#warn} but prints caller as well
|
5
6
|
#
|
6
7
|
# @param message [String] main message to print
|
7
8
|
# @return void
|
data/lib/alba/layout.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module Alba
|
2
2
|
# Representing nested attribute
|
3
|
+
# @api private
|
3
4
|
class NestedAttribute
|
4
5
|
# @param key_transformation [Symbol] determines how to transform keys
|
5
6
|
# @param block [Proc] class body
|
@@ -8,12 +9,14 @@ module Alba
|
|
8
9
|
@block = block
|
9
10
|
end
|
10
11
|
|
11
|
-
# @
|
12
|
-
|
12
|
+
# @param object [Object] the object being serialized
|
13
|
+
# @param params [Hash] params Hash inherited from Resource
|
14
|
+
# @return [Hash] hash serialized from running the class body in the object
|
15
|
+
def value(object:, params:)
|
13
16
|
resource_class = Alba.resource_class
|
14
17
|
resource_class.transform_keys(@key_transformation)
|
15
18
|
resource_class.class_eval(&@block)
|
16
|
-
resource_class.new(object).serializable_hash
|
19
|
+
resource_class.new(object, params: params).serializable_hash
|
17
20
|
end
|
18
21
|
end
|
19
22
|
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, _key_transformation_cascade: true, _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, _helper: 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
|
@@ -74,13 +80,14 @@ module Alba
|
|
74
80
|
end
|
75
81
|
end
|
76
82
|
|
77
|
-
# Returns a Hash correspondng {
|
83
|
+
# Returns a Hash correspondng {#serialize}
|
78
84
|
#
|
79
85
|
# @param root_key [Symbol, nil, true]
|
80
86
|
# @param meta [Hash] metadata for this seialization
|
81
87
|
# @return [Hash]
|
82
88
|
def as_json(root_key: nil, meta: {})
|
83
|
-
key = root_key.nil? ? fetch_key : root_key
|
89
|
+
key = root_key.nil? ? fetch_key : root_key
|
90
|
+
key = Alba.regularize_key(key)
|
84
91
|
if key && !key.empty?
|
85
92
|
h = {key => serializable_hash}
|
86
93
|
hash_with_metadata(h, meta)
|
@@ -99,15 +106,9 @@ module Alba
|
|
99
106
|
|
100
107
|
private
|
101
108
|
|
102
|
-
def encode(hash)
|
103
|
-
Alba.encoder.call(hash)
|
104
|
-
end
|
105
|
-
|
106
109
|
def _to_json(root_key, meta, options)
|
107
110
|
options.reject! { |k, _| %i[layout prefixes template status].include?(k) } # Rails specific guard
|
108
|
-
|
109
|
-
names = options.map { |k, v| k unless v.nil? }
|
110
|
-
names.compact!
|
111
|
+
names = options.filter_map { |k, v| k unless v.nil? }
|
111
112
|
unless names.empty?
|
112
113
|
names.sort!
|
113
114
|
names.map! { |s| "\"#{s}\"" }
|
@@ -134,7 +135,11 @@ module Alba
|
|
134
135
|
|
135
136
|
def serializable_hash_for_collection
|
136
137
|
if @_collection_key
|
137
|
-
@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
|
138
143
|
else
|
139
144
|
@object.each_with_object([], &collection_converter)
|
140
145
|
end
|
@@ -147,34 +152,26 @@ module Alba
|
|
147
152
|
end
|
148
153
|
|
149
154
|
def _key_for_collection
|
150
|
-
|
151
|
-
|
152
|
-
else
|
153
|
-
@_key_for_collection == true ? raise_root_key_inference_error : @_key_for_collection.to_s
|
154
|
-
end
|
155
|
+
k = @_key_for_collection == true ? resource_name(pluralized: true) : @_key_for_collection
|
156
|
+
Alba.regularize_key(k)
|
155
157
|
end
|
156
158
|
|
157
|
-
# @return [String]
|
158
159
|
def _key
|
159
|
-
|
160
|
-
|
161
|
-
else
|
162
|
-
@_key == true ? raise_root_key_inference_error : @_key.to_s
|
163
|
-
end
|
160
|
+
k = @_key == true ? resource_name(pluralized: false) : @_key
|
161
|
+
Alba.regularize_key(k)
|
164
162
|
end
|
165
163
|
|
166
164
|
def resource_name(pluralized: false)
|
167
|
-
class_name = self.class.name
|
168
165
|
inflector = Alba.inflector
|
169
|
-
|
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)
|
170
171
|
underscore_name = inflector.underscore(name)
|
171
172
|
pluralized ? inflector.pluralize(underscore_name) : underscore_name
|
172
173
|
end
|
173
174
|
|
174
|
-
def raise_root_key_inference_error
|
175
|
-
raise Alba::Error, 'You must set inflector when setting root key as true.'
|
176
|
-
end
|
177
|
-
|
178
175
|
def transforming_root_key?
|
179
176
|
@_transforming_root_key
|
180
177
|
end
|
@@ -237,14 +234,16 @@ module Alba
|
|
237
234
|
end
|
238
235
|
end
|
239
236
|
|
240
|
-
|
241
|
-
|
242
|
-
key = key.to_s
|
243
|
-
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
|
244
239
|
|
245
240
|
inflector = Alba.inflector
|
246
241
|
raise Alba::Error, 'Inflector is nil. You must set inflector before transforming keys.' unless inflector
|
247
242
|
|
243
|
+
Alba.regularize_key(_transform_key(inflector, key.to_s))
|
244
|
+
end
|
245
|
+
|
246
|
+
def _transform_key(inflector, key)
|
248
247
|
case @_transform_type # rubocop:disable Style/MissingElse
|
249
248
|
when :camel then inflector.camelize(key)
|
250
249
|
when :lower_camel then inflector.camelize_lower(key)
|
@@ -258,20 +257,31 @@ module Alba
|
|
258
257
|
when Symbol then fetch_attribute_from_object_and_resource(obj, attribute)
|
259
258
|
when Proc then instance_exec(obj, &attribute)
|
260
259
|
when Alba::Association then yield_if_within(attribute.name.to_sym) { |within| attribute.to_h(obj, params: params, within: within) }
|
261
|
-
when TypedAttribute
|
260
|
+
when TypedAttribute then attribute.value(obj)
|
261
|
+
when NestedAttribute then attribute.value(object: obj, params: params)
|
262
262
|
when ConditionalAttribute then attribute.with_passing_condition(resource: self, object: obj) { |attr| fetch_attribute(obj, key, attr) }
|
263
|
-
else
|
264
|
-
raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
263
|
+
else raise ::Alba::Error, "Unsupported type of attribute: #{attribute.class}"
|
265
264
|
end
|
266
265
|
value.nil? && nil_handler ? instance_exec(obj, key, attribute, &nil_handler) : value
|
267
266
|
end
|
268
267
|
|
268
|
+
# TODO: from version 3, `_fetch_attribute_from_resource_first` is default
|
269
269
|
def fetch_attribute_from_object_and_resource(obj, attribute)
|
270
|
+
_fetch_attribute_from_object_first(obj, attribute)
|
271
|
+
end
|
272
|
+
|
273
|
+
def _fetch_attribute_from_object_first(obj, attribute)
|
270
274
|
obj.__send__(attribute)
|
271
275
|
rescue NoMethodError
|
272
276
|
__send__(attribute, obj)
|
273
277
|
end
|
274
278
|
|
279
|
+
def _fetch_attribute_from_resource_first(obj, attribute)
|
280
|
+
__send__(attribute, obj)
|
281
|
+
rescue NoMethodError
|
282
|
+
obj.__send__(attribute)
|
283
|
+
end
|
284
|
+
|
275
285
|
def nil_handler
|
276
286
|
@_on_nil
|
277
287
|
end
|
@@ -327,8 +337,9 @@ module Alba
|
|
327
337
|
|
328
338
|
def assign_attributes(attrs, if_value)
|
329
339
|
attrs.each do |attr_name|
|
330
|
-
|
331
|
-
|
340
|
+
attr_name = attr_name.to_sym
|
341
|
+
attr = if_value ? ConditionalAttribute.new(body: attr_name, condition: if_value) : attr_name
|
342
|
+
@_attributes[attr_name] = attr
|
332
343
|
end
|
333
344
|
end
|
334
345
|
private :assign_attributes
|
@@ -373,8 +384,7 @@ module Alba
|
|
373
384
|
def association(name, condition = nil, resource: nil, key: nil, params: {}, **options, &block)
|
374
385
|
key_transformation = @_key_transformation_cascade ? @_transform_type : :none
|
375
386
|
assoc = Association.new(
|
376
|
-
name: name, condition: condition, resource: resource, params: params, nesting: nesting, key_transformation: key_transformation,
|
377
|
-
&block
|
387
|
+
name: name, condition: condition, resource: resource, params: params, nesting: nesting, key_transformation: key_transformation, helper: @_helper, &block
|
378
388
|
)
|
379
389
|
@_attributes[key&.to_sym || name.to_sym] = options[:if] ? ConditionalAttribute.new(body: assoc, condition: options[:if]) : assoc
|
380
390
|
end
|
@@ -387,7 +397,7 @@ module Alba
|
|
387
397
|
if name.nil?
|
388
398
|
nil
|
389
399
|
else
|
390
|
-
name.rpartition('::').first.
|
400
|
+
name.rpartition('::').first.then { |n| n.empty? ? nil : n }
|
391
401
|
end
|
392
402
|
end
|
393
403
|
private :nesting
|
@@ -502,6 +512,25 @@ module Alba
|
|
502
512
|
def on_nil(&block)
|
503
513
|
@_on_nil = block
|
504
514
|
end
|
515
|
+
|
516
|
+
# Define helper methods
|
517
|
+
#
|
518
|
+
# @param mod [Module] a module to extend
|
519
|
+
def helper(mod = @_helper || Module.new, &block)
|
520
|
+
mod.module_eval(&block) if block
|
521
|
+
extend mod
|
522
|
+
@_helper = mod
|
523
|
+
end
|
524
|
+
|
525
|
+
# DSL for alias, purely for readability
|
526
|
+
def prefer_resource_method!
|
527
|
+
alias_method :fetch_attribute_from_object_and_resource, :_fetch_attribute_from_resource_first
|
528
|
+
end
|
529
|
+
|
530
|
+
# DSL for alias, purely for readability
|
531
|
+
def prefer_object_method!
|
532
|
+
alias_method :fetch_attribute_from_object_and_resource, :_fetch_attribute_from_object_first
|
533
|
+
end
|
505
534
|
end
|
506
535
|
end
|
507
536
|
end
|
data/lib/alba/typed_attribute.rb
CHANGED
data/lib/alba/version.rb
CHANGED
data/lib/alba.rb
CHANGED
@@ -44,19 +44,29 @@ module Alba
|
|
44
44
|
# @param block [Block] resource block
|
45
45
|
# @return [String] serialized JSON string
|
46
46
|
# @raise [ArgumentError] if block is absent or `with` argument's type is wrong
|
47
|
-
def serialize(object, root_key: nil, &block)
|
48
|
-
|
49
|
-
|
50
|
-
resource = klass.new(object)
|
47
|
+
def serialize(object = nil, root_key: nil, &block)
|
48
|
+
resource = resource_with(object, &block)
|
51
49
|
resource.serialize(root_key: root_key)
|
52
50
|
end
|
53
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
|
+
|
54
64
|
# Enable inference for key and resource name
|
55
65
|
#
|
56
66
|
# @param with [Symbol, Class, Module] inflector
|
57
67
|
# When it's a Symbol, it sets inflector with given name
|
58
68
|
# When it's a Class or a Module, it sets given object to inflector
|
59
|
-
# @deprecated Use {
|
69
|
+
# @deprecated Use {.inflector=} instead
|
60
70
|
def enable_inference!(with:)
|
61
71
|
Alba::Deprecation.warn('Alba.enable_inference! is deprecated. Use `Alba.inflector=` instead.')
|
62
72
|
@inflector = inflector_from(with)
|
@@ -65,14 +75,14 @@ module Alba
|
|
65
75
|
|
66
76
|
# Disable inference for key and resource name
|
67
77
|
#
|
68
|
-
# @deprecated Use {
|
78
|
+
# @deprecated Use {.inflector=} instead
|
69
79
|
def disable_inference!
|
70
80
|
Alba::Deprecation.warn('Alba.disable_inference! is deprecated. Use `Alba.inflector = nil` instead.')
|
71
81
|
@inferring = false
|
72
82
|
@inflector = nil
|
73
83
|
end
|
74
84
|
|
75
|
-
# @deprecated Use {
|
85
|
+
# @deprecated Use {.inflector} instead
|
76
86
|
# @return [Boolean] whether inference is enabled or not
|
77
87
|
def inferring
|
78
88
|
Alba::Deprecation.warn('Alba.inferring is deprecated. Use `Alba.inflector` instead.')
|
@@ -107,16 +117,45 @@ module Alba
|
|
107
117
|
const_parent.const_get("#{inflector.classify(name)}Resource")
|
108
118
|
end
|
109
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
|
+
|
110
141
|
# Reset config variables
|
111
142
|
# Useful for test cleanup
|
112
143
|
def reset!
|
113
144
|
@encoder = default_encoder
|
145
|
+
@symbolize_keys = false
|
114
146
|
@_on_error = :raise
|
115
147
|
@_on_nil = nil
|
116
148
|
end
|
117
149
|
|
118
150
|
private
|
119
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
|
+
|
120
159
|
def inflector_from(name_or_module)
|
121
160
|
case name_or_module
|
122
161
|
when nil then nil
|
@@ -126,8 +165,7 @@ module Alba
|
|
126
165
|
when :dry
|
127
166
|
require 'dry/inflector'
|
128
167
|
Dry::Inflector.new
|
129
|
-
else
|
130
|
-
validate_inflector(name_or_module)
|
168
|
+
else validate_inflector(name_or_module)
|
131
169
|
end
|
132
170
|
end
|
133
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.4.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: 2023-
|
11
|
+
date: 2023-08-01 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.
|
@@ -19,6 +19,7 @@ extensions: []
|
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
21
|
- ".codeclimate.yml"
|
22
|
+
- ".editorconfig"
|
22
23
|
- ".github/ISSUE_TEMPLATE/bug_report.md"
|
23
24
|
- ".github/ISSUE_TEMPLATE/feature_request.md"
|
24
25
|
- ".github/dependabot.yml"
|
@@ -71,7 +72,7 @@ homepage: https://github.com/okuramasafumi/alba
|
|
71
72
|
licenses:
|
72
73
|
- MIT
|
73
74
|
metadata:
|
74
|
-
bug_tracker_uri: https://github.com/okuramasafumi/issues
|
75
|
+
bug_tracker_uri: https://github.com/okuramasafumi/alba/issues
|
75
76
|
changelog_uri: https://github.com/okuramasafumi/alba/blob/main/CHANGELOG.md
|
76
77
|
documentation_uri: https://rubydoc.info/github/okuramasafumi/alba
|
77
78
|
source_code_uri: https://github.com/okuramasafumi/alba
|
@@ -91,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
92
|
- !ruby/object:Gem::Version
|
92
93
|
version: '0'
|
93
94
|
requirements: []
|
94
|
-
rubygems_version: 3.4.
|
95
|
+
rubygems_version: 3.4.14
|
95
96
|
signing_key:
|
96
97
|
specification_version: 4
|
97
98
|
summary: Alba is the fastest JSON serializer for Ruby.
|