alba 2.4.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89ce66083a51c7811468f9435288afbc53f81418f21635af4393eaf1ac635d14
4
- data.tar.gz: 7344d77ba35c156a6a18abe6f5ff498b7a8cd24b460c5b3f6bf1f0739bd0c8aa
3
+ metadata.gz: d76423c125b10f3a012066e6df3924c1dde50c1842e78be87179d99da8707a9e
4
+ data.tar.gz: 23bba5622260ac6926a669b1d4d4aa44e6a4189838b2c77b23f6e846c56ea14f
5
5
  SHA512:
6
- metadata.gz: 33e534a676d589ba88db064b8ba57bbd10a20d268c417d8ed89fbc9b830f2861e4d3c1b46772d9151fce5dcff909e591f26f4fe1be77c247da4e1ceb4998d1cc
7
- data.tar.gz: 401030ee3033228b11d2b75fa411809e7707b0c8b6791c2fe9da8bf9ce290c290225cb4e23895bc31019cc8cead19ac20335fffb24682c58ebc7f09a8250999a
6
+ metadata.gz: d57037b9ea77550953a6e8dc4f6896e314ce1e63e36925ec960ebca1e2cce043f61a289091f09c74ef308de50d541e2b2624ce977dcf613165175328c546b32c
7
+ data.tar.gz: 21ab608e1844f3a575985927f81ed892f1cb50c8bf9008302c4572d1acc9c9bebbc66c1afce173af29a22b03f67a6e0b117197d037ae64061401e5a19d1779e6
@@ -38,7 +38,7 @@ jobs:
38
38
 
39
39
  steps:
40
40
  - name: Checkout repository
41
- uses: actions/checkout@v3
41
+ uses: actions/checkout@v4
42
42
 
43
43
  # Initializes the CodeQL tools for scanning.
44
44
  - name: Initialize CodeQL
@@ -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
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest, windows-latest, macos-latest]
11
- ruby: [2.7, 3.0, 3.1, 3.2, head, jruby, truffleruby]
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@v3
22
+ - uses: actions/checkout@v4
23
23
  - name: Set up Ruby
24
24
  uses: ruby/setup-ruby@v1
25
25
  with:
@@ -7,10 +7,10 @@ jobs:
7
7
  strategy:
8
8
  fail-fast: false
9
9
  matrix:
10
- ruby: [2.7, 3.0, 3.1]
10
+ ruby: ['3.0', 3.1, 3.2]
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
- - uses: actions/checkout@v3
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: 2.7
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` call without parenthese and that's absolutely fine
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 2.7 and higher and latest JRuby and TruffleRuby.
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
- "dummy!"
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
- ### Experimental support of types
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
- Note that this feature is experimental and interfaces are subject to change.
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 :id
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 :id
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 signiture of `serialize`.
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('>= 2.7.0')
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',
@@ -8,11 +8,12 @@ module Alba
8
8
  attr_reader :const_cache
9
9
  end
10
10
 
11
- attr_reader :object, :name
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>, nil] a resource class for the association
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
- @object = target.__send__(@name)
40
- @object = @condition.call(object, params, target) if @condition
41
- return if @object.nil?
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 @object.is_a?(Enumerable)
45
+ return to_h_with_each_resource(object, within, params) if object.is_a?(Enumerable)
45
46
 
46
- @resource.call(@object).new(@object, within: within, params: params).to_h
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 # rubocop:disable Style/MissingElse
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
- @object.map do |item|
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, attribute_from_association_body_or(fetched_attribute), &@condition)
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
- def attribute_from_association_body_or(fetched_attribute)
52
- @body.is_a?(Alba::Association) ? @body.object : fetched_attribute
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
- 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
- private_constant :DSLS
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
- DSLS.each do |name, initial|
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
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.0')
63
- # For Rails compatibility
64
- # The first options is a dummy parameter but required
65
- # You can pass empty Hash if you don't want to pass any arguments
66
- #
67
- # @see #serialize
68
- # @see https://github.com/rails/rails/blob/7-0-stable/actionpack/lib/action_controller/metal/renderers.rb#L156
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
- _fetch_attribute_from_object_first(obj, attribute)
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(*DSLS.keys)
303
+ attr_reader(*INTERNAL_VARIABLES.keys)
316
304
 
317
305
  # @private
318
306
  def inherited(subclass)
319
307
  super
320
- DSLS.each_key { |name| subclass.instance_variable_set("@#{name}", instance_variable_get("@#{name}").clone) }
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
- key_transformation = @_key_transformation_cascade ? @_transform_type : :none
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: key_transformation, helper: @_helper, &block
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
@@ -7,54 +7,27 @@ module Alba
7
7
  # @param converter [Proc]
8
8
  def initialize(name:, type:, converter:)
9
9
  @name = name
10
- @type = type
11
- @converter = case converter
12
- when true then default_converter
13
- when false, nil then null_converter
14
- else converter
15
- end
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
- value, result = check(object)
22
- result ? value : @converter.call(value)
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(value)}."
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
@@ -1,3 +1,3 @@
1
1
  module Alba
2
- VERSION = '2.4.0'.freeze
2
+ VERSION = '3.0.0'.freeze
3
3
  end
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
- const_parent.const_get("#{inflector.classify(name)}Resource")
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: :strict)
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: 2.4.0
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-08-01 00:00:00.000000000 Z
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: 2.7.0
90
+ version: 3.0.0
89
91
  required_rubygems_version: !ruby/object:Gem::Requirement
90
92
  requirements:
91
93
  - - ">="