alba 2.4.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
- - ">="
|