alba 2.4.2 → 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 +15 -2
- data/README.md +45 -14
- data/alba.gemspec +1 -1
- data/lib/alba/association.rb +5 -2
- 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 +30 -3
- metadata +4 -2
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,11 +6,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
-
## [
|
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)
|
10
18
|
|
11
19
|
### Fixed
|
12
20
|
|
13
|
-
- Multithread bug
|
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)
|
14
27
|
|
15
28
|
## [2.4.1] 2023-08-02
|
16
29
|
|
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
@@ -12,7 +12,8 @@ module Alba
|
|
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
|
@@ -52,7 +53,7 @@ module Alba
|
|
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,6 +61,8 @@ module Alba
|
|
60
61
|
self.class.const_cache.fetch(resource) do
|
61
62
|
self.class.const_cache[resource] = Object.const_get(resource)
|
62
63
|
end
|
64
|
+
else
|
65
|
+
raise Error, "Unexpected resource type: #{resource.class}"
|
63
66
|
end
|
64
67
|
end
|
65
68
|
|
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
@@ -116,7 +116,7 @@ module Alba
|
|
116
116
|
const_parent = nesting.nil? ? Object : Object.const_get(nesting)
|
117
117
|
begin
|
118
118
|
const_parent.const_get("#{inflector.classify(name)}Resource")
|
119
|
-
rescue # Retry for serializer
|
119
|
+
rescue NameError # Retry for serializer
|
120
120
|
const_parent.const_get("#{inflector.classify(name)}Serializer")
|
121
121
|
end
|
122
122
|
end
|
@@ -142,6 +142,23 @@ module Alba
|
|
142
142
|
@symbolize_keys ? key.to_sym : key.to_s
|
143
143
|
end
|
144
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
|
+
|
145
162
|
# Reset config variables
|
146
163
|
# Useful for test cleanup
|
147
164
|
def reset!
|
@@ -149,6 +166,8 @@ module Alba
|
|
149
166
|
@symbolize_keys = false
|
150
167
|
@_on_error = :raise
|
151
168
|
@_on_nil = nil
|
169
|
+
@types = {}
|
170
|
+
register_default_types
|
152
171
|
end
|
153
172
|
|
154
173
|
private
|
@@ -175,7 +194,7 @@ module Alba
|
|
175
194
|
|
176
195
|
def set_encoder_from_backend
|
177
196
|
@encoder = case @backend
|
178
|
-
when :oj, :oj_strict then try_oj
|
197
|
+
when :oj, :oj_strict then try_oj(mode: :strict)
|
179
198
|
when :oj_rails then try_oj(mode: :rails)
|
180
199
|
when :oj_default then try_oj(mode: :default)
|
181
200
|
when :active_support then try_active_support
|
@@ -185,7 +204,7 @@ module Alba
|
|
185
204
|
end
|
186
205
|
end
|
187
206
|
|
188
|
-
def try_oj(mode:
|
207
|
+
def try_oj(mode:)
|
189
208
|
require 'oj'
|
190
209
|
case mode
|
191
210
|
when :default
|
@@ -219,6 +238,14 @@ module Alba
|
|
219
238
|
|
220
239
|
inflector
|
221
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
|
222
249
|
end
|
223
250
|
|
224
251
|
reset!
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
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
|
@@ -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
|
- - ">="
|