alba 2.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/codeql-analysis.yml +1 -1
- data/.github/workflows/lint.yml +17 -0
- data/.github/workflows/main.yml +2 -2
- data/.github/workflows/perf.yml +2 -2
- data/.rubocop.yml +16 -2
- data/CHANGELOG.md +25 -0
- data/README.md +45 -14
- data/alba.gemspec +1 -1
- data/lib/alba/association.rb +16 -12
- data/lib/alba/conditional_attribute.rb +14 -3
- data/lib/alba/resource.rb +19 -28
- data/lib/alba/type.rb +43 -0
- data/lib/alba/typed_attribute.rb +11 -38
- data/lib/alba/version.rb +1 -1
- data/lib/alba.rb +34 -3
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d76423c125b10f3a012066e6df3924c1dde50c1842e78be87179d99da8707a9e
|
|
4
|
+
data.tar.gz: 23bba5622260ac6926a669b1d4d4aa44e6a4189838b2c77b23f6e846c56ea14f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d57037b9ea77550953a6e8dc4f6896e314ce1e63e36925ec960ebca1e2cce043f61a289091f09c74ef308de50d541e2b2624ce977dcf613165175328c546b32c
|
|
7
|
+
data.tar.gz: 21ab608e1844f3a575985927f81ed892f1cb50c8bf9008302c4572d1acc9c9bebbc66c1afce173af29a22b03f67a6e0b117197d037ae64061401e5a19d1779e6
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: Lint
|
|
2
|
+
|
|
3
|
+
on: [push,pull_request]
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
build:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
steps:
|
|
9
|
+
- uses: actions/checkout@v4
|
|
10
|
+
- name: Set up Ruby
|
|
11
|
+
uses: ruby/setup-ruby@v1
|
|
12
|
+
with:
|
|
13
|
+
ruby-version: 3.0 # The lowest version Alba supports
|
|
14
|
+
bundler-cache: true
|
|
15
|
+
- name: Run RuboCop
|
|
16
|
+
run: |
|
|
17
|
+
bundle exec rubocop
|
data/.github/workflows/main.yml
CHANGED
|
@@ -8,7 +8,7 @@ jobs:
|
|
|
8
8
|
fail-fast: false
|
|
9
9
|
matrix:
|
|
10
10
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
11
|
-
ruby: [
|
|
11
|
+
ruby: ['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
|
|
@@ -19,7 +19,7 @@ jobs:
|
|
|
19
19
|
env: # $BUNDLE_GEMFILE must be set at the job level, so it is set for all steps
|
|
20
20
|
BUNDLE_GEMFILE: ${{ (matrix.gemfile == 'without_active_support' && 'gemfiles/without_active_support.gemfile') || (matrix.gemfile == 'without_oj' && 'gemfiles/without_oj.gemfile') || null }}
|
|
21
21
|
steps:
|
|
22
|
-
- uses: actions/checkout@
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
23
|
- name: Set up Ruby
|
|
24
24
|
uses: ruby/setup-ruby@v1
|
|
25
25
|
with:
|
data/.github/workflows/perf.yml
CHANGED
|
@@ -7,10 +7,10 @@ jobs:
|
|
|
7
7
|
strategy:
|
|
8
8
|
fail-fast: false
|
|
9
9
|
matrix:
|
|
10
|
-
ruby: [
|
|
10
|
+
ruby: ['3.0', 3.1, 3.2]
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
steps:
|
|
13
|
-
- uses: actions/checkout@
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
14
|
- name: Set up Ruby
|
|
15
15
|
uses: ruby/setup-ruby@v1
|
|
16
16
|
with:
|
data/.rubocop.yml
CHANGED
|
@@ -22,7 +22,7 @@ AllCops:
|
|
|
22
22
|
- 'script/**/*.rb'
|
|
23
23
|
NewCops: enable
|
|
24
24
|
EnabledByDefault: true
|
|
25
|
-
TargetRubyVersion:
|
|
25
|
+
TargetRubyVersion: 3.0
|
|
26
26
|
|
|
27
27
|
# Items in Gemfile is dev dependencies and we don't have to specify versions.
|
|
28
28
|
Bundler/GemVersion:
|
|
@@ -62,6 +62,12 @@ Metrics:
|
|
|
62
62
|
|
|
63
63
|
# `Resource` module is a core module and its length tends to be long...
|
|
64
64
|
# `Alba` main module is also long because it has all parts of configuration
|
|
65
|
+
Metrics/ClassLength:
|
|
66
|
+
Exclude:
|
|
67
|
+
- 'lib/alba/resource.rb'
|
|
68
|
+
- 'lib/alba.rb'
|
|
69
|
+
- 'test/**/*.rb' # Neec to specify this
|
|
70
|
+
|
|
65
71
|
Metrics/ModuleLength:
|
|
66
72
|
Exclude:
|
|
67
73
|
- 'lib/alba/resource.rb'
|
|
@@ -93,6 +99,7 @@ Style/ConstantVisibility:
|
|
|
93
99
|
- 'lib/alba/version.rb'
|
|
94
100
|
- 'test/**/*.rb'
|
|
95
101
|
|
|
102
|
+
# Copyright is in README
|
|
96
103
|
Style/Copyright:
|
|
97
104
|
Enabled: false
|
|
98
105
|
|
|
@@ -100,34 +107,41 @@ Style/Copyright:
|
|
|
100
107
|
Style/DisableCopsWithinSourceCodeDirective:
|
|
101
108
|
Enabled: false
|
|
102
109
|
|
|
110
|
+
# Test files doesn't need to have documentation
|
|
103
111
|
Style/Documentation:
|
|
104
112
|
Exclude:
|
|
105
113
|
- 'test/**/*'
|
|
106
114
|
|
|
115
|
+
# In README it's so obvious
|
|
107
116
|
Style/DocumentationMethod:
|
|
108
117
|
Exclude:
|
|
109
118
|
- 'README.md'
|
|
110
119
|
|
|
120
|
+
# This might be true in the future, but not many good things
|
|
111
121
|
Style/FrozenStringLiteralComment:
|
|
112
122
|
Enabled: false
|
|
113
123
|
|
|
124
|
+
# I don't want to think about error class in example code
|
|
114
125
|
Style/ImplicitRuntimeError:
|
|
115
126
|
Exclude:
|
|
116
127
|
- 'README.md'
|
|
117
128
|
|
|
129
|
+
# We use it, don't we?
|
|
118
130
|
Style/InlineComment:
|
|
119
131
|
Enabled: false
|
|
120
132
|
|
|
121
133
|
Style/MethodCallWithArgsParentheses:
|
|
122
134
|
AllowedMethods: ['require', 'require_relative', 'include', 'extend', 'puts', 'p', 'warn', 'raise', 'send', 'public_send', 'alias_method']
|
|
123
135
|
Exclude:
|
|
124
|
-
# There are so many `attributes`
|
|
136
|
+
# There are so many calls like `attributes` and `register_type` without parenthese and that's absolutely fine
|
|
125
137
|
- 'test/**/*.rb'
|
|
138
|
+
- 'README.md'
|
|
126
139
|
|
|
127
140
|
# There are so many cases we just want `if` expression!
|
|
128
141
|
Style/MissingElse:
|
|
129
142
|
EnforcedStyle: case
|
|
130
143
|
|
|
144
|
+
# It's example code, please forgive us
|
|
131
145
|
Style/OptionalBooleanParameter:
|
|
132
146
|
Exclude:
|
|
133
147
|
- 'README.md'
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [3.0.0] 2023-10-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Custom type [#333](https://github.com/okuramasafumi/alba/pull/333)
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Prefer resource method [#323](https://github.com/okuramasafumi/alba/pull/323)
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Multithread bug [No PR](https://github.com/okuramasafumi/alba/commit/d20ed9efbf2f99827c12b8a07308e2f5aea6ab6d)
|
|
22
|
+
- This is a critical bug that can cause data corruption.
|
|
23
|
+
|
|
24
|
+
### Removed
|
|
25
|
+
|
|
26
|
+
- Drop support for Ruby 2 series [No PR](https://github.com/okuramasafumi/alba/commit/20be222555bde69c31fa9cbe4408b3f638cd7580)
|
|
27
|
+
|
|
28
|
+
## [2.4.1] 2023-08-02
|
|
29
|
+
|
|
30
|
+
#### Fixed
|
|
31
|
+
|
|
32
|
+
- Fix the bug of resource name inference for classes whose name end with "Serializer" [No PR](https://github.com/okuramasafumi/alba/commit/1695af4351981725231fd071aaef5b2e4174fb26)
|
|
33
|
+
|
|
9
34
|
## [2.4.0] 2023-08-02
|
|
10
35
|
|
|
11
36
|
### Added
|
data/README.md
CHANGED
|
@@ -83,6 +83,12 @@ Alba is easy to use because there are only a few methods to remember. It's also
|
|
|
83
83
|
|
|
84
84
|
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).
|
|
85
85
|
|
|
86
|
+
### Other reasons
|
|
87
|
+
|
|
88
|
+
- Dependency free, no need to install `oj` or `activesupport` while Alba works well with them
|
|
89
|
+
- Well tested, the test coverage is 99%
|
|
90
|
+
- Well maintained, gettings frequent update and new releases (see [version history](https://rubygems.org/gems/alba/versions))
|
|
91
|
+
|
|
86
92
|
## Installation
|
|
87
93
|
|
|
88
94
|
Add this line to your application's Gemfile:
|
|
@@ -101,7 +107,7 @@ Or install it yourself as:
|
|
|
101
107
|
|
|
102
108
|
## Supported Ruby versions
|
|
103
109
|
|
|
104
|
-
Alba supports CRuby
|
|
110
|
+
Alba supports CRuby 3.0 and higher and latest JRuby and TruffleRuby.
|
|
105
111
|
|
|
106
112
|
## Documentation
|
|
107
113
|
|
|
@@ -130,13 +136,13 @@ Alba's configuration is fairly simple.
|
|
|
130
136
|
|
|
131
137
|
Backend is the actual part serializing an object into JSON. Alba supports these backends.
|
|
132
138
|
|
|
133
|
-
|name|description|requires_external_gem|
|
|
134
|
-
|
|
135
|
-
|`oj`, `oj_strict`|Using Oj in `strict` mode|Yes(C extension)
|
|
136
|
-
|`oj_rails`|It's `oj` but in `rails` mode|Yes(C extension)
|
|
137
|
-
|`oj_default`|It's `oj` but respects mode set by users|Yes(C extension)
|
|
138
|
-
|`active_support`|For Rails compatibility|Yes
|
|
139
|
-
|`default`, `json`|Using `json` gem|No
|
|
139
|
+
|name|description|requires_external_gem| encoder|
|
|
140
|
+
|--|--|--|--|
|
|
141
|
+
|`oj`, `oj_strict`|Using Oj in `strict` mode|Yes(C extension)|`Oj.dump(object, mode: :strict)`|
|
|
142
|
+
|`oj_rails`|It's `oj` but in `rails` mode|Yes(C extension)|`Oj.dump(object, mode: :rails)`|
|
|
143
|
+
|`oj_default`|It's `oj` but respects mode set by users|Yes(C extension)|`Oj.dump(object)`|
|
|
144
|
+
|`active_support`|For Rails compatibility|Yes|`ActiveSupport::JSON.encode(object)`|
|
|
145
|
+
|`default`, `json`|Using `json` gem|No|`JSON.generate(object)`|
|
|
140
146
|
|
|
141
147
|
You can set a backend like this:
|
|
142
148
|
|
|
@@ -144,6 +150,12 @@ You can set a backend like this:
|
|
|
144
150
|
Alba.backend = :oj
|
|
145
151
|
```
|
|
146
152
|
|
|
153
|
+
This is equivalent as:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
Alba.encoder = ->(object) { Oj.dump(object, mode: :strict) }
|
|
157
|
+
```
|
|
158
|
+
|
|
147
159
|
#### Encoder configuration
|
|
148
160
|
|
|
149
161
|
You can also set JSON encoder directly with a Proc.
|
|
@@ -284,7 +296,7 @@ class User
|
|
|
284
296
|
end
|
|
285
297
|
|
|
286
298
|
def name_with_email
|
|
287
|
-
|
|
299
|
+
'dummy!'
|
|
288
300
|
end
|
|
289
301
|
end
|
|
290
302
|
|
|
@@ -1231,7 +1243,7 @@ You can control circular associations with `within` option. `within` option is a
|
|
|
1231
1243
|
|
|
1232
1244
|
For more details, please refer to [test code](https://github.com/okuramasafumi/alba/blob/main/test/usecases/circular_association_test.rb)
|
|
1233
1245
|
|
|
1234
|
-
###
|
|
1246
|
+
### Types
|
|
1235
1247
|
|
|
1236
1248
|
You can validate and convert input with types.
|
|
1237
1249
|
|
|
@@ -1270,7 +1282,26 @@ UserResource.new(user).serialize
|
|
|
1270
1282
|
# => TypeError, 'Attribute bio is expected to be String but actually nil.'
|
|
1271
1283
|
```
|
|
1272
1284
|
|
|
1273
|
-
|
|
1285
|
+
#### Custom types
|
|
1286
|
+
|
|
1287
|
+
You can define custom types to abstract data conversion logic. To define custom types, you can use `Alba.register_type` like below.
|
|
1288
|
+
|
|
1289
|
+
```ruby
|
|
1290
|
+
# Typically in initializer
|
|
1291
|
+
Alba.register_type :iso8601, converter: ->(time) { time.iso8601(3) }, auto_convert: true
|
|
1292
|
+
```
|
|
1293
|
+
|
|
1294
|
+
Then use it as regular types.
|
|
1295
|
+
|
|
1296
|
+
```rb
|
|
1297
|
+
class UserResource
|
|
1298
|
+
include Alba::Resource
|
|
1299
|
+
|
|
1300
|
+
attributes :id, created_at: :iso8601
|
|
1301
|
+
end
|
|
1302
|
+
```
|
|
1303
|
+
|
|
1304
|
+
You now get `created_at` attribute with `iso8601` format!
|
|
1274
1305
|
|
|
1275
1306
|
### Collection serialization into Hash
|
|
1276
1307
|
|
|
@@ -1376,7 +1407,7 @@ class ApplicationResource
|
|
|
1376
1407
|
include Alba::Resource
|
|
1377
1408
|
|
|
1378
1409
|
def self.with_id
|
|
1379
|
-
attributes
|
|
1410
|
+
attributes(:id)
|
|
1380
1411
|
end
|
|
1381
1412
|
end
|
|
1382
1413
|
|
|
@@ -1401,7 +1432,7 @@ class ApplicationResource
|
|
|
1401
1432
|
|
|
1402
1433
|
helper do
|
|
1403
1434
|
def with_id
|
|
1404
|
-
attributes
|
|
1435
|
+
attributes(:id)
|
|
1405
1436
|
end
|
|
1406
1437
|
end
|
|
1407
1438
|
end
|
|
@@ -1561,7 +1592,7 @@ FooResource.new(foo).serialize
|
|
|
1561
1592
|
# => "{:id=>1}" is printed
|
|
1562
1593
|
```
|
|
1563
1594
|
|
|
1564
|
-
Here, we override `serialize` method with `prepend`. In overridden method we print the result of `serializable_hash` that gives the basic hash for serialization to `serialize` method. Using `...` allows us to override without knowing method
|
|
1595
|
+
Here, we override `serialize` method with `prepend`. In overridden method we print the result of `serializable_hash` that gives the basic hash for serialization to `serialize` method. Using `...` allows us to override without knowing method signature of `serialize`.
|
|
1565
1596
|
|
|
1566
1597
|
Don't forget calling `super` in this way.
|
|
1567
1598
|
|
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('>=
|
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0')
|
|
14
14
|
|
|
15
15
|
spec.metadata = {
|
|
16
16
|
'bug_tracker_uri' => 'https://github.com/okuramasafumi/alba/issues',
|
data/lib/alba/association.rb
CHANGED
|
@@ -8,11 +8,12 @@ module Alba
|
|
|
8
8
|
attr_reader :const_cache
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
attr_reader :
|
|
11
|
+
attr_reader :name
|
|
12
12
|
|
|
13
13
|
# @param name [Symbol, String] name of the method to fetch association
|
|
14
14
|
# @param condition [Proc, nil] a proc filtering data
|
|
15
|
-
# @param resource [Class<Alba::Resource>,
|
|
15
|
+
# @param resource [Class<Alba::Resource>, Proc, String, Symbol, nil]
|
|
16
|
+
# a resource class for the association, a proc returning a resource class or a name of the resource
|
|
16
17
|
# @param params [Hash] params override for the association
|
|
17
18
|
# @param nesting [String] a namespace where source class is inferred with
|
|
18
19
|
# @param key_transformation [Symbol] key transformation type
|
|
@@ -36,29 +37,32 @@ module Alba
|
|
|
36
37
|
# @return [Hash]
|
|
37
38
|
def to_h(target, within: nil, params: {})
|
|
38
39
|
params = params.merge(@params)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return if
|
|
40
|
+
object = target.__send__(@name)
|
|
41
|
+
object = @condition.call(object, params, target) if @condition
|
|
42
|
+
return if object.nil?
|
|
42
43
|
|
|
43
44
|
if @resource.is_a?(Proc)
|
|
44
|
-
return to_h_with_each_resource(within, params) if
|
|
45
|
+
return to_h_with_each_resource(object, within, params) if object.is_a?(Enumerable)
|
|
45
46
|
|
|
46
|
-
@resource.call(
|
|
47
|
+
@resource.call(object).new(object, within: within, params: params).to_h
|
|
47
48
|
else
|
|
48
|
-
to_h_with_constantize_resource(within, params)
|
|
49
|
+
to_h_with_constantize_resource(object, within, params)
|
|
49
50
|
end
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
private
|
|
53
54
|
|
|
54
55
|
def constantize(resource)
|
|
55
|
-
case resource
|
|
56
|
+
case resource
|
|
56
57
|
when Class
|
|
57
58
|
resource
|
|
58
59
|
when Symbol, String
|
|
60
|
+
Object.const_get(resource)
|
|
59
61
|
self.class.const_cache.fetch(resource) do
|
|
60
62
|
self.class.const_cache[resource] = Object.const_get(resource)
|
|
61
63
|
end
|
|
64
|
+
else
|
|
65
|
+
raise Error, "Unexpected resource type: #{resource.class}"
|
|
62
66
|
end
|
|
63
67
|
end
|
|
64
68
|
|
|
@@ -76,13 +80,13 @@ module Alba
|
|
|
76
80
|
end
|
|
77
81
|
end
|
|
78
82
|
|
|
79
|
-
def to_h_with_each_resource(within, params)
|
|
80
|
-
|
|
83
|
+
def to_h_with_each_resource(object, within, params)
|
|
84
|
+
object.map do |item|
|
|
81
85
|
@resource.call(item).new(item, within: within, params: params).to_h
|
|
82
86
|
end
|
|
83
87
|
end
|
|
84
88
|
|
|
85
|
-
def to_h_with_constantize_resource(within, params)
|
|
89
|
+
def to_h_with_constantize_resource(object, within, params)
|
|
86
90
|
@resource = constantize(@resource)
|
|
87
91
|
@resource.new(object, params: params, within: within).to_h
|
|
88
92
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require_relative 'association'
|
|
2
2
|
require_relative 'constants'
|
|
3
|
+
require 'ostruct'
|
|
3
4
|
|
|
4
5
|
module Alba
|
|
5
6
|
# Represents attribute with `if` option
|
|
@@ -23,7 +24,7 @@ module Alba
|
|
|
23
24
|
fetched_attribute = yield(@body)
|
|
24
25
|
return fetched_attribute unless with_two_arity_proc_condition
|
|
25
26
|
|
|
26
|
-
return Alba::REMOVE_KEY unless resource.instance_exec(object,
|
|
27
|
+
return Alba::REMOVE_KEY unless resource.instance_exec(object, objectize(fetched_attribute), &@condition)
|
|
27
28
|
|
|
28
29
|
fetched_attribute
|
|
29
30
|
end
|
|
@@ -48,8 +49,18 @@ module Alba
|
|
|
48
49
|
@condition.is_a?(Proc) && @condition.arity >= 2
|
|
49
50
|
end
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
# OpenStruct is used as a simple solution for converting Hash or Array of Hash into an object
|
|
53
|
+
# Using OpenStruct is not good in general, but in this case there's no other solution
|
|
54
|
+
def objectize(fetched_attribute)
|
|
55
|
+
return fetched_attribute unless @body.is_a?(Alba::Association)
|
|
56
|
+
|
|
57
|
+
if fetched_attribute.is_a?(Array)
|
|
58
|
+
fetched_attribute.map do |hash|
|
|
59
|
+
OpenStruct.new(hash)
|
|
60
|
+
end
|
|
61
|
+
else
|
|
62
|
+
OpenStruct.new(fetched_attribute)
|
|
63
|
+
end
|
|
53
64
|
end
|
|
54
65
|
end
|
|
55
66
|
end
|
data/lib/alba/resource.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require_relative 'association'
|
|
2
2
|
require_relative 'conditional_attribute'
|
|
3
3
|
require_relative 'constants'
|
|
4
|
+
require_relative 'type'
|
|
4
5
|
require_relative 'typed_attribute'
|
|
5
6
|
require_relative 'nested_attribute'
|
|
6
7
|
require_relative 'deprecation'
|
|
@@ -11,8 +12,8 @@ module Alba
|
|
|
11
12
|
module Resource
|
|
12
13
|
# @!parse include InstanceMethods
|
|
13
14
|
# @!parse extend ClassMethods
|
|
14
|
-
|
|
15
|
-
private_constant :
|
|
15
|
+
INTERNAL_VARIABLES = {_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
|
|
16
|
+
private_constant :INTERNAL_VARIABLES
|
|
16
17
|
|
|
17
18
|
WITHIN_DEFAULT = Object.new.freeze
|
|
18
19
|
private_constant :WITHIN_DEFAULT
|
|
@@ -24,7 +25,7 @@ module Alba
|
|
|
24
25
|
setup_method_body = 'private def _setup;'
|
|
25
26
|
base.class_eval do
|
|
26
27
|
# Initialize
|
|
27
|
-
|
|
28
|
+
INTERNAL_VARIABLES.each do |name, initial|
|
|
28
29
|
instance_variable_set("@#{name}", initial.dup) unless instance_variable_defined?("@#{name}")
|
|
29
30
|
setup_method_body << "@#{name} = self.class.#{name};"
|
|
30
31
|
end
|
|
@@ -59,25 +60,13 @@ module Alba
|
|
|
59
60
|
serialize_with(as_json(root_key: root_key, meta: meta))
|
|
60
61
|
end
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def to_json(options, root_key: nil, meta: {})
|
|
70
|
-
_to_json(root_key, meta, options)
|
|
71
|
-
end
|
|
72
|
-
else
|
|
73
|
-
# For Rails compatibility
|
|
74
|
-
# The first options is a dummy parameter
|
|
75
|
-
#
|
|
76
|
-
# @see #serialize
|
|
77
|
-
# @see https://github.com/rails/rails/blob/7-0-stable/actionpack/lib/action_controller/metal/renderers.rb#L156
|
|
78
|
-
def to_json(options = {}, root_key: nil, meta: {})
|
|
79
|
-
_to_json(root_key, meta, options)
|
|
80
|
-
end
|
|
63
|
+
# For Rails compatibility
|
|
64
|
+
# The first options is a dummy parameter
|
|
65
|
+
#
|
|
66
|
+
# @see #serialize
|
|
67
|
+
# @see https://github.com/rails/rails/blob/7-0-stable/actionpack/lib/action_controller/metal/renderers.rb#L156
|
|
68
|
+
def to_json(options = {}, root_key: nil, meta: {})
|
|
69
|
+
_to_json(root_key, meta, options)
|
|
81
70
|
end
|
|
82
71
|
|
|
83
72
|
# Returns a Hash correspondng {#serialize}
|
|
@@ -265,9 +254,8 @@ module Alba
|
|
|
265
254
|
value.nil? && nil_handler ? instance_exec(obj, key, attribute, &nil_handler) : value
|
|
266
255
|
end
|
|
267
256
|
|
|
268
|
-
# TODO: from version 3, `_fetch_attribute_from_resource_first` is default
|
|
269
257
|
def fetch_attribute_from_object_and_resource(obj, attribute)
|
|
270
|
-
|
|
258
|
+
_fetch_attribute_from_resource_first(obj, attribute)
|
|
271
259
|
end
|
|
272
260
|
|
|
273
261
|
def _fetch_attribute_from_object_first(obj, attribute)
|
|
@@ -312,12 +300,12 @@ module Alba
|
|
|
312
300
|
|
|
313
301
|
# Class methods
|
|
314
302
|
module ClassMethods
|
|
315
|
-
attr_reader(*
|
|
303
|
+
attr_reader(*INTERNAL_VARIABLES.keys)
|
|
316
304
|
|
|
317
305
|
# @private
|
|
318
306
|
def inherited(subclass)
|
|
319
307
|
super
|
|
320
|
-
|
|
308
|
+
INTERNAL_VARIABLES.each_key { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}").clone) }
|
|
321
309
|
end
|
|
322
310
|
|
|
323
311
|
# Defining methods for DSLs and disable parameter number check since for users' benefits increasing params is fine
|
|
@@ -382,9 +370,9 @@ module Alba
|
|
|
382
370
|
# @return [void]
|
|
383
371
|
# @see Alba::Association#initialize
|
|
384
372
|
def association(name, condition = nil, resource: nil, key: nil, params: {}, **options, &block)
|
|
385
|
-
|
|
373
|
+
transformation = @_key_transformation_cascade ? @_transform_type : :none
|
|
386
374
|
assoc = Association.new(
|
|
387
|
-
name: name, condition: condition, resource: resource, params: params, nesting: nesting, key_transformation:
|
|
375
|
+
name: name, condition: condition, resource: resource, params: params, nesting: nesting, key_transformation: transformation, helper: @_helper, &block
|
|
388
376
|
)
|
|
389
377
|
@_attributes[key&.to_sym || name.to_sym] = options[:if] ? ConditionalAttribute.new(body: assoc, condition: options[:if]) : assoc
|
|
390
378
|
end
|
|
@@ -533,4 +521,7 @@ module Alba
|
|
|
533
521
|
end
|
|
534
522
|
end
|
|
535
523
|
end
|
|
524
|
+
|
|
525
|
+
Serializer = Resource
|
|
526
|
+
public_constant :Serializer
|
|
536
527
|
end
|
data/lib/alba/type.rb
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Alba
|
|
2
|
+
# Representing type itself, combined with {Alba::TypedAttribute}
|
|
3
|
+
class Type
|
|
4
|
+
attr_reader :name
|
|
5
|
+
attr_writer :auto_convert
|
|
6
|
+
|
|
7
|
+
# @param name [Symbol, String] name of the type
|
|
8
|
+
# @param check [Proc, Boolean] proc to check type
|
|
9
|
+
# If false, type check is skipped
|
|
10
|
+
# @param converter [Proc] proc to convert type
|
|
11
|
+
# @param auto_convert [Boolean] whether to convert type automatically
|
|
12
|
+
def initialize(name, check:, converter:, auto_convert: false)
|
|
13
|
+
@name = name
|
|
14
|
+
@check = check
|
|
15
|
+
@converter = converter
|
|
16
|
+
@auto_convert = auto_convert
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Type check
|
|
20
|
+
#
|
|
21
|
+
# @param value [Object] value to check
|
|
22
|
+
# @return [Boolean] the result of type check
|
|
23
|
+
def check(value)
|
|
24
|
+
@check == false ? false : @check.call(value)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Type convert
|
|
28
|
+
# If @auto_convert is true, @convert proc is called with obj
|
|
29
|
+
# Otherwise, it raises an exception that is caught by {Alba::TypedAttribute}
|
|
30
|
+
#
|
|
31
|
+
# @param obj [Object] object to convert
|
|
32
|
+
def convert(obj)
|
|
33
|
+
@auto_convert ? @converter.call(obj) : raise(TypeError)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Enable auto convert with given converter
|
|
37
|
+
# @param converter [Proc] proc to convert type
|
|
38
|
+
def auto_convert_with(converter)
|
|
39
|
+
@converter = converter
|
|
40
|
+
@auto_convert = true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/alba/typed_attribute.rb
CHANGED
|
@@ -7,54 +7,27 @@ module Alba
|
|
|
7
7
|
# @param converter [Proc]
|
|
8
8
|
def initialize(name:, type:, converter:)
|
|
9
9
|
@name = name
|
|
10
|
-
|
|
11
|
-
@
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
t = Alba.find_type(type)
|
|
11
|
+
@type = case converter
|
|
12
|
+
when true then t.dup.tap { _1.auto_convert = true }
|
|
13
|
+
when false, nil then t
|
|
14
|
+
else
|
|
15
|
+
t.dup.tap { _1.auto_convert_with(converter) }
|
|
16
|
+
end
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
# @param object [Object] target to check and convert type with
|
|
19
20
|
# @return [String, Integer, Boolean] type-checked or type-converted object
|
|
20
21
|
def value(object)
|
|
21
|
-
|
|
22
|
-
result
|
|
22
|
+
v = object.__send__(@name)
|
|
23
|
+
result = @type.check(v)
|
|
24
|
+
result ? v : @type.convert(v)
|
|
23
25
|
rescue TypeError
|
|
24
|
-
raise TypeError, "Attribute #{@name} is expected to be #{@type} but actually #{display_value_for(
|
|
26
|
+
raise TypeError, "Attribute #{@name} is expected to be #{@type.name} but actually #{display_value_for(v)}."
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
private
|
|
28
30
|
|
|
29
|
-
def check(object)
|
|
30
|
-
value = object.__send__(@name)
|
|
31
|
-
type_correct = case @type
|
|
32
|
-
when :String, ->(klass) { klass == String } then value.is_a?(String)
|
|
33
|
-
when :Integer, ->(klass) { klass == Integer } then value.is_a?(Integer)
|
|
34
|
-
when :Boolean then [true, false].include?(value)
|
|
35
|
-
else
|
|
36
|
-
raise Alba::UnsupportedType, "Unknown type: #{@type}"
|
|
37
|
-
end
|
|
38
|
-
[value, type_correct]
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def default_converter
|
|
42
|
-
case @type
|
|
43
|
-
when :String, ->(klass) { klass == String }
|
|
44
|
-
->(object) { object.to_s }
|
|
45
|
-
when :Integer, ->(klass) { klass == Integer }
|
|
46
|
-
->(object) { Integer(object) }
|
|
47
|
-
when :Boolean
|
|
48
|
-
->(object) { !!object }
|
|
49
|
-
else
|
|
50
|
-
raise Alba::UnsupportedType, "Unknown type: #{@type}"
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def null_converter
|
|
55
|
-
->(_) { raise TypeError }
|
|
56
|
-
end
|
|
57
|
-
|
|
58
31
|
def display_value_for(value)
|
|
59
32
|
value.nil? ? 'nil' : value.class.name
|
|
60
33
|
end
|
data/lib/alba/version.rb
CHANGED
data/lib/alba.rb
CHANGED
|
@@ -114,7 +114,11 @@ module Alba
|
|
|
114
114
|
raise Alba::Error, 'Inference is disabled so Alba cannot infer resource name. Set inflector before use.' unless Alba.inflector
|
|
115
115
|
|
|
116
116
|
const_parent = nesting.nil? ? Object : Object.const_get(nesting)
|
|
117
|
-
|
|
117
|
+
begin
|
|
118
|
+
const_parent.const_get("#{inflector.classify(name)}Resource")
|
|
119
|
+
rescue NameError # Retry for serializer
|
|
120
|
+
const_parent.const_get("#{inflector.classify(name)}Serializer")
|
|
121
|
+
end
|
|
118
122
|
end
|
|
119
123
|
|
|
120
124
|
# Configure Alba to symbolize keys
|
|
@@ -138,6 +142,23 @@ module Alba
|
|
|
138
142
|
@symbolize_keys ? key.to_sym : key.to_s
|
|
139
143
|
end
|
|
140
144
|
|
|
145
|
+
# Register types, used for both builtin and custom types
|
|
146
|
+
#
|
|
147
|
+
# @see Alba::Type
|
|
148
|
+
# @return [void]
|
|
149
|
+
def register_type(name, check: false, converter: nil, auto_convert: false)
|
|
150
|
+
@types[name] = Type.new(name, check: check, converter: converter, auto_convert: auto_convert)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Find type by name
|
|
154
|
+
#
|
|
155
|
+
# @return [Alba::Type]
|
|
156
|
+
def find_type(name)
|
|
157
|
+
@types.fetch(name) do
|
|
158
|
+
raise(Alba::UnsupportedType, "Unknown type: #{name}")
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
141
162
|
# Reset config variables
|
|
142
163
|
# Useful for test cleanup
|
|
143
164
|
def reset!
|
|
@@ -145,6 +166,8 @@ module Alba
|
|
|
145
166
|
@symbolize_keys = false
|
|
146
167
|
@_on_error = :raise
|
|
147
168
|
@_on_nil = nil
|
|
169
|
+
@types = {}
|
|
170
|
+
register_default_types
|
|
148
171
|
end
|
|
149
172
|
|
|
150
173
|
private
|
|
@@ -171,7 +194,7 @@ module Alba
|
|
|
171
194
|
|
|
172
195
|
def set_encoder_from_backend
|
|
173
196
|
@encoder = case @backend
|
|
174
|
-
when :oj, :oj_strict then try_oj
|
|
197
|
+
when :oj, :oj_strict then try_oj(mode: :strict)
|
|
175
198
|
when :oj_rails then try_oj(mode: :rails)
|
|
176
199
|
when :oj_default then try_oj(mode: :default)
|
|
177
200
|
when :active_support then try_active_support
|
|
@@ -181,7 +204,7 @@ module Alba
|
|
|
181
204
|
end
|
|
182
205
|
end
|
|
183
206
|
|
|
184
|
-
def try_oj(mode:
|
|
207
|
+
def try_oj(mode:)
|
|
185
208
|
require 'oj'
|
|
186
209
|
case mode
|
|
187
210
|
when :default
|
|
@@ -215,6 +238,14 @@ module Alba
|
|
|
215
238
|
|
|
216
239
|
inflector
|
|
217
240
|
end
|
|
241
|
+
|
|
242
|
+
def register_default_types # rubocop:disable Metrics/AbcSize
|
|
243
|
+
register_type(:String, check: ->(obj) { obj.is_a?(String) }, converter: ->(obj) { obj.to_s })
|
|
244
|
+
register_type(String, check: ->(obj) { obj.is_a?(String) }, converter: ->(obj) { obj.to_s })
|
|
245
|
+
register_type(:Integer, check: ->(obj) { obj.is_a?(Integer) }, converter: ->(obj) { Integer(obj) })
|
|
246
|
+
register_type(Integer, check: ->(obj) { obj.is_a?(Integer) }, converter: ->(obj) { Integer(obj) })
|
|
247
|
+
register_type(:Boolean, check: ->(obj) { [true, false].include?(obj) }, converter: ->(obj) { !!obj })
|
|
248
|
+
end
|
|
218
249
|
end
|
|
219
250
|
|
|
220
251
|
reset!
|
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:
|
|
4
|
+
version: 3.0.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-10-11 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.
|
|
@@ -24,6 +24,7 @@ files:
|
|
|
24
24
|
- ".github/ISSUE_TEMPLATE/feature_request.md"
|
|
25
25
|
- ".github/dependabot.yml"
|
|
26
26
|
- ".github/workflows/codeql-analysis.yml"
|
|
27
|
+
- ".github/workflows/lint.yml"
|
|
27
28
|
- ".github/workflows/main.yml"
|
|
28
29
|
- ".github/workflows/perf.yml"
|
|
29
30
|
- ".gitignore"
|
|
@@ -62,6 +63,7 @@ files:
|
|
|
62
63
|
- lib/alba/nested_attribute.rb
|
|
63
64
|
- lib/alba/railtie.rb
|
|
64
65
|
- lib/alba/resource.rb
|
|
66
|
+
- lib/alba/type.rb
|
|
65
67
|
- lib/alba/typed_attribute.rb
|
|
66
68
|
- lib/alba/version.rb
|
|
67
69
|
- logo/alba-card.png
|
|
@@ -85,7 +87,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
85
87
|
requirements:
|
|
86
88
|
- - ">="
|
|
87
89
|
- !ruby/object:Gem::Version
|
|
88
|
-
version:
|
|
90
|
+
version: 3.0.0
|
|
89
91
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
92
|
requirements:
|
|
91
93
|
- - ">="
|